001package org.jsoup.nodes;
002
003import org.jsoup.Connection;
004import org.jsoup.Jsoup;
005import org.jsoup.helper.HttpConnection;
006import org.jsoup.helper.Validate;
007import org.jsoup.parser.Tag;
008import org.jsoup.select.Elements;
009
010import java.util.ArrayList;
011import java.util.List;
012
013/**
014 * A HTML Form Element provides ready access to the form fields/controls that are associated with it. It also allows a
015 * form to easily be submitted.
016 */
017public class FormElement extends Element {
018    private final Elements elements = new Elements();
019
020    /**
021     * Create a new, standalone form element.
022     *
023     * @param tag        tag of this element
024     * @param baseUri    the base URI
025     * @param attributes initial attributes
026     */
027    public FormElement(Tag tag, String baseUri, Attributes attributes) {
028        super(tag, baseUri, attributes);
029    }
030
031    /**
032     * Get the list of form control elements associated with this form.
033     * @return form controls associated with this element.
034     */
035    public Elements elements() {
036        return elements;
037    }
038
039    /**
040     * Add a form control element to this form.
041     * @param element form control to add
042     * @return this form element, for chaining
043     */
044    public FormElement addElement(Element element) {
045        elements.add(element);
046        return this;
047    }
048
049    /**
050     * Prepare to submit this form. A Connection object is created with the request set up from the form values. You
051     * can then set up other options (like user-agent, timeout, cookies), then execute it.
052     * @return a connection prepared from the values of this form.
053     * @throws IllegalArgumentException if the form's absolute action URL cannot be determined. Make sure you pass the
054     * document's base URI when parsing.
055     */
056    public Connection submit() {
057        String action = hasAttr("action") ? absUrl("action") : baseUri();
058        Validate.notEmpty(action, "Could not determine a form action URL for submit. Ensure you set a base URI when parsing.");
059        Connection.Method method = attr("method").toUpperCase().equals("POST") ?
060                Connection.Method.POST : Connection.Method.GET;
061
062        return Jsoup.connect(action)
063                .data(formData())
064                .method(method);
065    }
066
067    /**
068     * Get the data that this form submits. The returned list is a copy of the data, and changes to the contents of the
069     * list will not be reflected in the DOM.
070     * @return a list of key vals
071     */
072    public List<Connection.KeyVal> formData() {
073        ArrayList<Connection.KeyVal> data = new ArrayList<>();
074
075        // iterate the form control elements and accumulate their values
076        for (Element el: elements) {
077            if (!el.tag().isFormSubmittable()) continue; // contents are form listable, superset of submitable
078            if (el.hasAttr("disabled")) continue; // skip disabled form inputs
079            String name = el.attr("name");
080            if (name.length() == 0) continue;
081            String type = el.attr("type");
082
083            if ("select".equals(el.tagName())) {
084                Elements options = el.select("option[selected]");
085                boolean set = false;
086                for (Element option: options) {
087                    data.add(HttpConnection.KeyVal.create(name, option.val()));
088                    set = true;
089                }
090                if (!set) {
091                    Element option = el.select("option").first();
092                    if (option != null)
093                        data.add(HttpConnection.KeyVal.create(name, option.val()));
094                }
095            } else if ("checkbox".equalsIgnoreCase(type) || "radio".equalsIgnoreCase(type)) {
096                // only add checkbox or radio if they have the checked attribute
097                if (el.hasAttr("checked")) {
098                    final String val = el.val().length() >  0 ? el.val() : "on";
099                    data.add(HttpConnection.KeyVal.create(name, val));
100                }
101            } else {
102                data.add(HttpConnection.KeyVal.create(name, el.val()));
103            }
104        }
105        return data;
106    }
107}