1using Gee;
2
3namespace Xmpp {
4
5public abstract class StanzaEntry {
6    protected const string ANSI_COLOR_END = "\x1b[0m";
7    protected const string ANSI_COLOR_GREEN = "\x1b[32m";
8    protected const string ANSI_COLOR_YELLOW = "\x1b[33m";
9    protected const string ANSI_COLOR_GRAY = "\x1b[37m";
10
11    public string? ns_uri;
12    public string name;
13    public string? val;
14
15    public string? encoded_val {
16        owned get {
17            if (val == null) return null;
18            return ((!)val).replace("&", "&amp;").replace("\"", "&quot;").replace("'", "&apos;").replace("<", "&lt;").replace(">", "&gt;");
19        }
20        set {
21            if (value == null) {
22                val = null;
23                return;
24            }
25            string tmp = ((!)value).replace("&gt;", ">").replace("&lt;", "<").replace("&apos;","'").replace("&quot;","\"");
26            while (tmp.contains("&#")) {
27                int start = tmp.index_of("&#");
28                int end = tmp.index_of(";", start);
29                if (end < start) break;
30                unichar num = -1;
31                if (tmp[start+2]=='x') {
32                    tmp.substring(start+3, start-end-3).scanf("%x", &num);
33                } else {
34                    num = int.parse(tmp.substring(start+2, start-end-2));
35                }
36                tmp = tmp.splice(start, end, num.to_string());
37            }
38            val = tmp.replace("&amp;", "&");
39        }
40    }
41
42    public virtual unowned string? get_string_content() {
43        return val;
44    }
45
46    public virtual string to_string(int i = 0) {
47        return get_string_content() ?? "(null)";
48    }
49}
50
51public class StanzaNode : StanzaEntry {
52    public Gee.List<StanzaNode> sub_nodes = new ArrayList<StanzaNode>();
53    public Gee.List<StanzaAttribute> attributes = new ArrayList<StanzaAttribute>();
54    public bool has_nodes = false;
55    public bool pseudo = false;
56
57    internal StanzaNode() {
58    }
59
60    public StanzaNode.build(string name, string ns_uri = "jabber:client", ArrayList<StanzaNode>? nodes = null, ArrayList<StanzaAttribute>? attrs = null) {
61        this.ns_uri = ns_uri;
62        this.name = name;
63        if (nodes != null) this.sub_nodes.add_all((!)nodes);
64        if (attrs != null) this.attributes.add_all((!)attrs);
65    }
66
67    public StanzaNode.text(string text) {
68        this.name = "#text";
69        this.val = text;
70    }
71
72    public StanzaNode.encoded_text(string text) {
73        this.name = "#text";
74        this.encoded_val = text;
75    }
76
77    public StanzaNode add_self_xmlns() {
78        if (ns_uri == null) return this;
79        return put_attribute("xmlns", (!)ns_uri);
80    }
81
82    public unowned string? get_attribute(string name, string? ns_uri = null) {
83        string _name = name;
84        string? _ns_uri = ns_uri;
85        if (_ns_uri == null) {
86            if (_name.contains(":")) {
87                var lastIndex = _name.last_index_of_char(':');
88                _ns_uri = _name.substring(0, lastIndex);
89                _name = _name.substring(lastIndex + 1);
90            } else {
91                _ns_uri = this.ns_uri;
92            }
93        }
94        foreach (var attr in attributes) {
95            if (attr.ns_uri == (!)_ns_uri && attr.name == _name) return attr.val;
96        }
97        return null;
98    }
99
100    public int get_attribute_int(string name, int def = -1, string? ns_uri = null) {
101        string? res = get_attribute(name, ns_uri);
102        if (res == null) return def;
103        return int.parse((!)res);
104    }
105
106    public uint get_attribute_uint(string name, uint def = 0, string? ns_uri = null) {
107        string? res = get_attribute(name, ns_uri);
108        if (res == null) return def;
109        return (uint) long.parse((!)res);
110    }
111
112    public bool get_attribute_bool(string name, bool def = false, string? ns_uri = null) {
113        string? res = get_attribute(name, ns_uri);
114        if (res == null) return def;
115        return ((!)res).down() == "true" || res == "1";
116    }
117
118    public StanzaAttribute? get_attribute_raw(string name, string? ns_uri = null) {
119        string _name = name;
120        string? _ns_uri = ns_uri;
121        if (_ns_uri == null) {
122            if (_name.contains(":")) {
123                var lastIndex = _name.last_index_of_char(':');
124                _ns_uri = _name.substring(0, lastIndex);
125                _name = _name.substring(lastIndex + 1);
126            } else {
127                _ns_uri = this.ns_uri;
128            }
129        }
130        foreach (var attr in attributes) {
131            if (attr.ns_uri == _ns_uri && attr.name == _name) return attr;
132        }
133        return null;
134    }
135
136    public Gee.List<StanzaAttribute> get_attributes_by_ns_uri(string ns_uri) {
137        ArrayList<StanzaAttribute> ret = new ArrayList<StanzaAttribute> ();
138        foreach (var attr in attributes) {
139            if (attr.ns_uri == ns_uri) ret.add(attr);
140        }
141        return ret;
142    }
143
144    public unowned string? get_deep_attribute(...) {
145        va_list l = va_list();
146        StanzaAttribute? res = get_deep_attribute_(va_list.copy(l));
147        if (res == null) return null;
148        return ((!)res).val;
149    }
150
151    public StanzaAttribute? get_deep_attribute_(va_list l) {
152        StanzaNode node = this;
153        string? attribute_name = l.arg();
154        if (attribute_name == null) return null;
155        while (true) {
156            string? s = l.arg();
157            if (s == null) break;
158            StanzaNode? node_tmp = node.get_subnode((!)attribute_name);
159            if (node_tmp == null) return null;
160            node = (!)node_tmp;
161            attribute_name = s;
162        }
163        return node.get_attribute_raw((!)attribute_name);
164    }
165
166    public StanzaNode? get_subnode(string name, string? ns_uri = null, bool recurse = false) {
167        string _name = name;
168        string? _ns_uri = ns_uri;
169        if (ns_uri == null) {
170            if (_name.contains(":")) {
171                var lastIndex = _name.last_index_of_char(':');
172                _ns_uri = _name.substring(0, lastIndex);
173                _name = _name.substring(lastIndex + 1);
174            } else {
175                _ns_uri = this.ns_uri;
176            }
177        }
178        foreach (var node in sub_nodes) {
179            if (node.ns_uri == _ns_uri && node.name == _name) return node;
180            if (recurse) {
181                var x = node.get_subnode(_name, _ns_uri, recurse);
182                if (x != null) return x;
183            }
184        }
185        return null;
186    }
187
188    public Gee.List<StanzaNode> get_subnodes(string name, string? ns_uri = null, bool recurse = false) {
189        ArrayList<StanzaNode> ret = new ArrayList<StanzaNode>();
190        string _name = name;
191        string? _ns_uri = ns_uri;
192        if (ns_uri == null) {
193            if (_name.contains(":")) {
194                var lastIndex = _name.last_index_of_char(':');
195                _ns_uri = _name.substring(0, lastIndex);
196                _name = _name.substring(lastIndex + 1);
197            } else {
198                _ns_uri = this.ns_uri;
199            }
200        }
201        foreach (var node in sub_nodes) {
202            if (node.ns_uri == _ns_uri && node.name == _name) ret.add(node);
203            if (recurse) {
204                ret.add_all(node.get_subnodes(_name, _ns_uri, recurse));
205            }
206        }
207        return ret;
208    }
209
210    public StanzaNode? get_deep_subnode(...) {
211        va_list l = va_list();
212        return get_deep_subnode_(va_list.copy(l));
213    }
214
215    public StanzaNode? get_deep_subnode_(va_list l) {
216        StanzaNode node = this;
217        while (true) {
218            string? s = l.arg();
219            if (s == null) break;
220            StanzaNode? node_tmp = node.get_subnode((!)s);
221            if (node_tmp == null) return null;
222            node = (!)node_tmp;
223        }
224        return node;
225    }
226
227    public Gee.List<StanzaNode> get_deep_subnodes(...) {
228        va_list l = va_list();
229        return get_deep_subnodes_(va_list.copy(l));
230    }
231
232    public Gee.List<StanzaNode> get_deep_subnodes_(va_list l) {
233        StanzaNode node = this;
234        string? subnode_name = l.arg();
235        if (subnode_name == null) return new ArrayList<StanzaNode>();
236        while (true) {
237            string? s = l.arg();
238            if (s == null) break;
239            StanzaNode? node_tmp = node.get_subnode((!)subnode_name);
240            if (node_tmp == null) return new ArrayList<StanzaNode>();
241            node = (!)node_tmp;
242            subnode_name = s;
243        }
244        return node.get_subnodes((!)subnode_name);
245    }
246
247    public Gee.List<StanzaNode> get_all_subnodes() {
248        return sub_nodes;
249    }
250
251    public Gee.List<StanzaNode> get_deep_all_subnodes(...) {
252        va_list l = va_list();
253        StanzaNode? node = get_deep_subnode_(va_list.copy(l));
254        if (node != null) return ((!)node).get_all_subnodes();
255        return new ArrayList<StanzaNode>();
256    }
257
258    public void add_attribute(StanzaAttribute attr) {
259        attributes.add(attr);
260    }
261
262    public override unowned string? get_string_content() {
263        if (val != null) return val;
264        if (sub_nodes.size == 1) return sub_nodes[0].get_string_content();
265        return null;
266    }
267
268    public unowned string? get_deep_string_content(...) {
269        va_list l = va_list();
270        StanzaNode? node = get_deep_subnode_(va_list.copy(l));
271        if (node != null) return ((!)node).get_string_content();
272        return null;
273    }
274
275    public StanzaNode put_attribute(string name, string val, string? ns_uri = null) {
276        string? _ns_uri = ns_uri;
277        if (name == "xmlns") _ns_uri = XMLNS_URI;
278        if (_ns_uri == null) _ns_uri = this.ns_uri;
279        if (_ns_uri == null) return this;
280        attributes.add(new StanzaAttribute.build((!)_ns_uri, name, val));
281        return this;
282    }
283
284    /**
285    *    Set only occurrence
286    **/
287    public void set_attribute(string name, string val, string? ns_uri = null) {
288        if (ns_uri == null) ns_uri = this.ns_uri;
289        foreach (var attr in attributes) {
290            if (attr.ns_uri == ns_uri && attr.name == name) {
291                attr.val = val;
292                return;
293            }
294        }
295        put_attribute(name, val, ns_uri);
296    }
297
298    public StanzaNode put_node(StanzaNode node) {
299        sub_nodes.add(node);
300        return this;
301    }
302
303    public bool equals(StanzaNode other) {
304        if (other.name != name) return false;
305        if (other.val != val) return false;
306        if (name == "#text") return true;
307        if (other.ns_uri != ns_uri) return false;
308
309        if (other.sub_nodes.size != sub_nodes.size) return false;
310        for (int i = 0; i < sub_nodes.size; i++) {
311            if (!other.sub_nodes[i].equals(sub_nodes[i])) return false;
312        }
313
314        if (other.attributes.size != attributes.size) return false;
315        for (int i = 0; i < attributes.size; i++) {
316            if (!other.attributes[i].equals(attributes[i])) return false;
317        }
318
319        return true;
320    }
321
322    private const string TAG_START_BEGIN_FORMAT = "%s<{%s}:%s";
323    private const string TAG_START_EMPTY_END = " />\n";
324    private const string TAG_START_CONTENT_END = ">\n";
325    private const string TAG_END_FORMAT = "%s</{%s}:%s>\n";
326    private const string TAG_ANSI_START_BEGIN_FORMAT = "%s"+ANSI_COLOR_YELLOW+"<"+ANSI_COLOR_GRAY+"{%s}:"+ANSI_COLOR_YELLOW+"%s"+ANSI_COLOR_END;
327    private const string TAG_ANSI_START_BEGIN_NO_NS_FORMAT = "%s"+ANSI_COLOR_YELLOW+"<%s"+ANSI_COLOR_END;
328    private const string TAG_ANSI_START_EMPTY_END = ANSI_COLOR_YELLOW+" />"+ANSI_COLOR_END+"\n";
329    private const string TAG_ANSI_START_CONTENT_END = ANSI_COLOR_YELLOW+">"+ANSI_COLOR_END+"\n";
330    private const string TAG_ANSI_END_FORMAT = "%s"+ANSI_COLOR_YELLOW+"</"+ANSI_COLOR_GRAY+"{%s}:"+ANSI_COLOR_YELLOW+"%s>"+ANSI_COLOR_END+"\n";
331    private const string TAG_ANSI_END_NO_NS_FORMAT = "%s"+ANSI_COLOR_YELLOW+"</%s>"+ANSI_COLOR_END+"\n";
332
333    internal string printf(int i, string fmt_start_begin, string start_empty_end, string start_content_end, string fmt_end, string fmt_attr, bool no_ns = false) {
334        string indent = string.nfill (i * 2, ' ');
335        if (name == "#text") {
336            if (((!)val).length > 1000) {
337                return indent + "[... retracted for brevity ...]\n";
338            }
339            return indent + ((!)val).replace("\n", indent + "\n") + "\n";
340        }
341        var sb = new StringBuilder();
342        if (no_ns) {
343            sb.append_printf(fmt_start_begin, indent, name);
344        } else {
345            sb.append_printf(fmt_start_begin, indent, (!)ns_uri, name);
346        }
347        foreach (StanzaAttribute attr in attributes) {
348            sb.append_printf(" %s", attr.printf(fmt_attr, no_ns));
349        }
350        if (!has_nodes && sub_nodes.size == 0) {
351            sb.append(start_empty_end);
352        } else {
353            sb.append(start_content_end);
354            if (sub_nodes.size != 0) {
355                foreach (StanzaNode subnode in sub_nodes) {
356                    sb.append(subnode.printf(i+1, fmt_start_begin, start_empty_end, start_content_end, fmt_end, fmt_attr, no_ns));
357                }
358                if (no_ns) {
359                    sb.append_printf(fmt_end, indent, name);
360                } else {
361                    sb.append_printf(fmt_end, indent, (!)ns_uri, name);
362                }
363            }
364        }
365        return sb.str;
366    }
367
368    public override string to_string(int i = 0) {
369        return printf(i, TAG_START_BEGIN_FORMAT, TAG_START_EMPTY_END, TAG_START_CONTENT_END, TAG_END_FORMAT, StanzaAttribute.ATTRIBUTE_STRING_FORMAT);
370    }
371
372    public string to_ansi_string(bool hide_ns = false, int i = 0) {
373        if (hide_ns) {
374            return printf(i, TAG_ANSI_START_BEGIN_NO_NS_FORMAT, TAG_ANSI_START_EMPTY_END, TAG_ANSI_START_CONTENT_END, TAG_ANSI_END_NO_NS_FORMAT, StanzaAttribute.ATTRIBUTE_STRING_ANSI_NO_NS_FORMAT, true);
375        } else {
376            return printf(i, TAG_ANSI_START_BEGIN_FORMAT, TAG_ANSI_START_EMPTY_END, TAG_ANSI_START_CONTENT_END, TAG_ANSI_END_FORMAT, StanzaAttribute.ATTRIBUTE_STRING_ANSI_FORMAT);
377        }
378    }
379
380    public string to_xml(NamespaceState? state = null) throws XmlError {
381        NamespaceState my_state = state ?? new NamespaceState.for_stanza();
382        if (name == "#text") return val == null ? "" : (!)encoded_val;
383        my_state = my_state.push();
384        foreach (var xmlns in get_attributes_by_ns_uri (XMLNS_URI)) {
385            if (xmlns.val == null) continue;
386            if (xmlns.name == "xmlns") {
387                my_state.set_current((!)xmlns.val);
388            } else {
389                my_state.add_assoc((!)xmlns.val, xmlns.name);
390            }
391        }
392        var sb = new StringBuilder();
393        if (ns_uri == my_state.current_ns_uri) {
394            sb.append_printf("<%s", name);
395        } else {
396            sb.append_printf("<%s:%s", my_state.find_name ((!)ns_uri), name);
397        }
398        var attr_ns_state = new NamespaceState.with_current(my_state, (!)ns_uri);
399        foreach (StanzaAttribute attr in attributes) {
400            sb.append_printf(" %s", attr.to_xml(attr_ns_state));
401        }
402        if (!has_nodes && sub_nodes.size == 0) {
403            sb.append("/>");
404        } else {
405            sb.append(">");
406            if (sub_nodes.size != 0) {
407                foreach (StanzaNode subnode in sub_nodes) {
408                    sb.append(subnode.to_xml(my_state));
409                }
410                if (ns_uri == my_state.current_ns_uri) {
411                    sb.append(@"</$name>");
412                } else {
413                    sb.append_printf("</%s:%s>", my_state.find_name ((!)ns_uri), name);
414                }
415            }
416        }
417        my_state = my_state.pop();
418        return sb.str;
419    }
420}
421
422}
423