1using Gee;
2
3namespace Xmpp.Xep.DataForms {
4
5public const string NS_URI = "jabber:x:data";
6
7public class DataForm {
8
9    public StanzaNode stanza_node { get; set; }
10    public Gee.List<Field> fields = new ArrayList<Field>();
11    public string? form_type = null;
12
13    public StanzaNode get_submit_node() {
14        stanza_node.set_attribute("type", "submit");
15        return stanza_node;
16    }
17
18    public enum Type {
19        BOOLEAN,
20        FIXED,
21        HIDDEN,
22        JID_MULTI,
23        LIST_SINGLE,
24        LIST_MULTI,
25        TEXT_PRIVATE,
26        TEXT_SINGLE,
27    }
28
29    public class Option {
30        public string label { get; set; }
31        public string value { get; set; }
32
33        public Option(string label, string value) {
34            this.label = label;
35            this.value = value;
36        }
37    }
38
39    public class Field {
40        public StanzaNode node { get; set; }
41        public string? label {
42            get { return node.get_attribute("label", NS_URI); }
43            set { node.set_attribute("label", value); }
44        }
45        public virtual Type? type_ { get; internal set; default=null; }
46        public string? var {
47            get { return node.get_attribute("var", NS_URI); }
48            set { node.set_attribute("var", value); }
49        }
50
51        public Field() {
52            this.node = new StanzaNode.build("field", NS_URI);
53        }
54
55        public Field.from_node(StanzaNode node) {
56            this.node = node;
57        }
58
59        internal Gee.List<string> get_values() {
60            Gee.List<string> ret = new ArrayList<string>();
61            Gee.List<StanzaNode> value_nodes = node.get_subnodes("value", NS_URI);
62            foreach (StanzaNode node in value_nodes) {
63                ret.add(node.get_string_content());
64            }
65            return ret;
66        }
67
68        public string get_value_string() {
69            Gee.List<string> values = get_values();
70            return values.size > 0 ? values[0] : "";
71        }
72
73        public void set_value_string(string val) {
74            StanzaNode? value_node = node.get_subnode("value", NS_URI);
75            if (value_node == null) {
76                value_node = new StanzaNode.build("value", NS_URI);
77                node.put_node(value_node);
78            }
79            value_node.sub_nodes.clear();
80            value_node.put_node(new StanzaNode.text(val));
81        }
82
83        internal void add_value_string(string val) {
84            StanzaNode node = new StanzaNode.build("value");
85            node.put_node(new StanzaNode.text(val));
86        }
87
88        internal Gee.List<Option>? get_options() {
89            Gee.List<Option> ret = new ArrayList<Option>();
90            Gee.List<StanzaNode> option_nodes = node.get_subnodes("option", NS_URI);
91            foreach (StanzaNode node in option_nodes) {
92                Option option = new Option(node.get_attribute("label", NS_URI), node.get_subnode("value").get_string_content());
93                ret.add(option);
94            }
95            return ret;
96        }
97    }
98
99    public class BooleanField : Field {
100        public bool value {
101            get { return get_value_string() == "1"; }
102            set { set_value_string(value ? "1" : "0"); }
103        }
104        public BooleanField(StanzaNode node) {
105            base.from_node(node);
106            type_ = Type.BOOLEAN;
107        }
108    }
109
110    public class FixedField : Field {
111        public string value {
112            owned get { return get_value_string(); }
113            set { set_value_string(value); }
114        }
115        public FixedField(StanzaNode node) {
116            base.from_node(node);
117            type_ = Type.FIXED;
118        }
119    }
120
121    public class HiddenField : Field {
122        public HiddenField() {
123            base();
124            type_ = Type.HIDDEN;
125            node.put_attribute("type", "hidden");
126        }
127        public HiddenField.from_node(StanzaNode node) {
128            base.from_node(node);
129            type_ = Type.HIDDEN;
130        }
131    }
132
133    public class JidMultiField : Field {
134        public Gee.List<Option> options { owned get { return get_options(); } }
135        public Gee.List<string> value { get; set; }
136        public JidMultiField(StanzaNode node) {
137            base.from_node(node);
138            type_ = Type.JID_MULTI;
139        }
140    }
141
142    public class ListSingleField : Field {
143        public Gee.List<Option> options { owned get { return get_options(); } }
144        public string value {
145            owned get { return get_value_string(); }
146            set { set_value_string(value); }
147        }
148        public ListSingleField(StanzaNode node) {
149            base.from_node(node);
150            type_ = Type.LIST_SINGLE;
151            node.set_attribute("type", "list-single");
152        }
153    }
154
155    public class ListMultiField : Field {
156        public Gee.List<Option> options { owned get { return get_options(); } }
157        public Gee.List<string> value { get; set; }
158        public ListMultiField(StanzaNode node) {
159            base.from_node(node);
160            type_ = Type.LIST_MULTI;
161        }
162    }
163
164    public class TextPrivateField : Field {
165        public string value {
166            owned get { return get_value_string(); }
167            set { set_value_string(value); }
168        }
169        public TextPrivateField(StanzaNode node) {
170            base.from_node(node);
171            type_ = Type.TEXT_PRIVATE;
172        }
173    }
174
175    public class TextSingleField : Field {
176        public string value {
177            owned get { return get_value_string(); }
178            set { set_value_string(value); }
179        }
180        public TextSingleField(StanzaNode node) {
181            base.from_node(node);
182            type_ = Type.TEXT_SINGLE;
183        }
184    }
185
186    // TODO text-multi
187
188    internal DataForm.from_node(StanzaNode node) {
189        this.stanza_node = node;
190
191        Gee.List<StanzaNode> field_nodes = node.get_subnodes("field", NS_URI);
192        foreach (StanzaNode field_node in field_nodes) {
193            string? type = field_node.get_attribute("type", NS_URI);
194            switch (type) {
195                case "boolean":
196                    fields.add(new BooleanField(field_node)); break;
197                case "fixed":
198                    fields.add(new FixedField(field_node)); break;
199                case "hidden":
200                    HiddenField field = new HiddenField.from_node(field_node);
201                    if (field.var == "FORM_TYPE") {
202                        this.form_type = field.get_value_string();
203                        break;
204                    }
205                    fields.add(field); break;
206                case "jid-multi":
207                    fields.add(new JidMultiField(field_node)); break;
208                case "list-single":
209                    fields.add(new ListSingleField(field_node)); break;
210                case "list-multi":
211                    fields.add(new ListMultiField(field_node)); break;
212                case "text-private":
213                    fields.add(new TextPrivateField(field_node)); break;
214                case "text-single":
215                    fields.add(new TextSingleField(field_node)); break;
216            }
217        }
218    }
219
220    internal DataForm() {
221        this.stanza_node = new StanzaNode.build("x", NS_URI).add_self_xmlns();
222    }
223
224    public static DataForm? create_from_node(StanzaNode node) {
225        return new DataForm.from_node(node);
226    }
227
228    public void add_field(Field field) {
229        fields.add(field);
230        stanza_node.put_node(field.node);
231    }
232}
233
234}
235