/**
* Copyright (C) 2012-2014 Gist Labs, LLC. (http://gistlabs.com)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package com.gistlabs.mechanize.document.html.form;
import static com.gistlabs.mechanize.util.css.CSSHelper.byIdOrName;
import java.io.File;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import com.gistlabs.mechanize.Resource;
import com.gistlabs.mechanize.document.documentElements.AbstractDocumentElement;
import com.gistlabs.mechanize.document.node.Node;
import com.gistlabs.mechanize.parameters.Parameters;
import com.gistlabs.mechanize.requestor.RequestBuilder;
/**
* Represents a form.
*
* @author Martin Kersten<Martin.Kersten.mk@gmail.com>
* @since 2012-09-12
*/
public class Form extends AbstractDocumentElement implements Iterable<FormElement> {
private List<FormElement> elements = new ArrayList<FormElement>();
public Form(Resource page, Node formNode) {
super(page, formNode);
analyse();
}
private void analyse() {
List<? extends Node> nodes = getNode().findAll("input, textarea, select");
for(Node node : nodes) {
FormElement formElement = newFormElement(node);
if(formElement != null)
this.elements.add(formElement);
}
}
private FormElement newFormElement(Node node) {
if(node.getName().equalsIgnoreCase("input") && node.hasAttribute("type")) {
String type = node.getAttribute("type");
if(type.equalsIgnoreCase("text"))
return new Text(this, node);
else if(type.equalsIgnoreCase("search"))
return new Search(this, node);
else if(type.equalsIgnoreCase("email"))
return new Email(this, node);
else if(type.equalsIgnoreCase("password"))
return new Password(this, node);
else if(type.equalsIgnoreCase("file"))
return new Upload(this, node);
else if(type.equalsIgnoreCase("hidden"))
return new Hidden(this, node);
else if(type.equalsIgnoreCase("submit"))
return new SubmitButton(this, node);
else if(type.equalsIgnoreCase("image"))
return new SubmitImage(this, node);
else if(type.equalsIgnoreCase("checkbox"))
return new Checkbox(this, node);
else if(type.equalsIgnoreCase("radio"))
return new RadioButton(this, node);
else
return new FormElement(this, node);
}
else if(node.getName().equalsIgnoreCase("input") && !node.hasAttribute("type")) {
//Used to map input without type to text field (as required by google.com search form)
return new Text(this, node);
}
else if(node.getName().equalsIgnoreCase("textarea"))
return new TextArea(this, node);
else if(node.getName().equalsIgnoreCase("select"))
return new Select(this, node);
else
return null;
}
public boolean isDoPost() {
return getNode().hasAttribute("method") && getNode().getAttribute("method").equalsIgnoreCase("post");
}
public boolean isMultiPart() {
return getNode().hasAttribute("enctype") && getNode().getAttribute("enctype").equalsIgnoreCase("multipart/form-data");
}
public String getUri() {
String uri = null;
if(getNode().hasAttribute("action"))
uri = absoluteUrl(getNode().getAttribute("action"));
else
uri = getResource().getUri().toString();
return uri;
}
/** Returns the element with the given name (case sensitive) or null. */
public FormElement get(String nameOrId) {
return find(byIdOrName(nameOrId));
}
public FormElement find(String csss) {
for(FormElement element : elements)
if(element.matches(csss))
return element;
return null;
}
@SuppressWarnings("unchecked")
public <T> T find(String csss, Class<T> clazz) {
for(FormElement element : elements) {
if ((clazz==null || clazz.isInstance(element)) && element.matches(csss)) {
return (T)element;
}
}
return null;
}
@SuppressWarnings("unchecked")
private <T> T find(String csss, String value, Class<T> clazz) {
for(FormElement element : elements) {
if ((clazz==null || clazz.isInstance(element)) && element.matches(csss)) {
if (value==null || value.equals(element.getValue())) {
return (T)element;
}
}
}
return null;
}
@SuppressWarnings("unchecked")
public <T> List<T> findAll(String csss, Class<T> clazz) {
List<T> result = new ArrayList<T>();
for(FormElement element : elements) {
if ((clazz==null || clazz.isInstance(element)) && element.matches(csss)) {
result.add((T)element);
}
}
return result;
}
public Email findEmail(String csss) {
return find(csss, Email.class);
}
public Select findSelect(String csss) {
return find(csss, Select.class);
}
public Checkbox findCheckbox(String csss) {
return find(csss, Checkbox.class);
}
public Search findSearch(String csss) {
return find(csss, Search.class);
}
public SubmitImage findSubmitImage(String csss) {
return find(csss, SubmitImage.class);
}
public Upload findUpload(String csss) {
return find(csss, Upload.class);
}
public RadioButton findRadioButton(String csss) {
return find(csss, RadioButton.class);
}
public Checkbox findCheckbox(String csss, String value) {
return find(csss, value, Checkbox.class);
}
public RadioButton findRadioButton(String csss, String value) {
return find(csss, value, RadioButton.class);
}
public TextArea findTextArea(String csss) {
return find(csss, TextArea.class);
}
public SubmitButton findSubmitButton(String csss) {
return find(csss, SubmitButton.class);
}
public Checkbox getCheckbox(String nameOrId, String value) {
return get(nameOrId, value, Checkbox.class);
}
public RadioButton getRadioButton(String nameOrId) {
return get(nameOrId, RadioButton.class);
}
public RadioButton getRadioButton(String nameOrId, String value) {
return get(nameOrId, value, RadioButton.class);
}
public List<RadioButton> getRadioButtons(String nameOrId) {
return getAll(nameOrId, RadioButton.class);
}
@SuppressWarnings("unchecked")
private <T> T get(String nameOrId, Class<T> clazz) {
for(FormElement element : elements)
if(clazz.isInstance(element) && (nameOrId.equals(element.getName()) || nameOrId.equals(element.getId())))
return (T)element;
return null;
}
@SuppressWarnings("unchecked")
private <T> T get(String nameOrId, String value, Class<T> clazz) {
for(FormElement element : elements)
if(clazz.isInstance(element) && (nameOrId.equals(element.getName()) || nameOrId.equals(element.getId()))
&& (value == element.getValue() || (value != null && value.equals(element.getValue()))))
return (T)element;
return null;
}
@SuppressWarnings("unchecked")
private <T> List<T> getAll(String nameOrId, Class<T> clazz) {
List<T> result = new ArrayList<T>();
for(FormElement element : elements)
if(clazz.isInstance(element) && (nameOrId.equals(element.getName()) || nameOrId.equals(element.getId())))
result.add((T)element);
return result;
}
public <T extends Resource> T submit() {
return submit(this, composeParameters(null, null, 0, 0));
}
public <T extends Resource> T submit(SubmitButton button) {
return submit(this, composeParameters(button, null, 0, 0));
}
public <T extends Resource> T submit(SubmitImage image, int x, int y) {
return submit(this, composeParameters(null, image, x, y));
}
/** Returns the page object received as response to the form submit action. */
@SuppressWarnings("unchecked")
private <T extends Resource> T submit(Form form, Parameters parameters) {
RequestBuilder<Resource> request = doRequest(form.getUri()).set(parameters);
boolean doPost = form.isDoPost();
boolean multiPart = form.isMultiPart();
if(doPost && multiPart) {
request.multiPart();
addFiles(request, form);
}
return (T) (doPost ? request.post() : request.get());
}
private void addFiles(RequestBuilder<Resource> request, Form form) {
for(FormElement formElement : form) {
if(formElement instanceof Upload) {
Upload upload = (Upload)formElement;
if (upload.hasValue()) {
File file = upload.hasFileValue() ? upload.getFileValue() : new File(upload.getValue());
request.set(upload.getName(), file);
}
}
}
}
/** Returns the parameters containing all name value params beside submit button, image button, unchecked radio
* buttons and checkboxes and without Upload information. */
private Parameters composeParameters(SubmitButton button, SubmitImage image, int x, int y) {
Parameters params = new Parameters();
for(FormElement element : this) {
String name = element.getName();
String value = element.getValue();
if(element instanceof Checkable) {
if(((Checkable)element).isChecked())
params.add(name, value != null ? value : "");
}
else if(element instanceof Select) {
Select select = (Select)element;
for(Select.Option option : select.getOptions())
if(option.isSelected())
params.add(select.getName(), option.getValue() != null ? option.getValue() : "");
}
else if(!(element instanceof SubmitButton) && !(element instanceof SubmitImage) && !(element instanceof Upload)) {
if(name != null)
params.add(name, value != null ? value : "");
}
}
if(button != null && button.getName() != null)
params.add(button.getName(), button.getValue() != null ? button.getValue() : "");
if(image != null) {
if(image.getValue() != null && image.getValue().length() > 1)
params.add(image.getName(), image.getValue());
params.add(image.getName() + ".x", "" + x);
params.add(image.getName() + ".y", "" + y);
}
return params;
}
public Iterator<FormElement> iterator() {
return elements.iterator();
}
}