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("&", "&").replace("\"", """).replace("'", "'").replace("<", "<").replace(">", ">"); 19 } 20 set { 21 if (value == null) { 22 val = null; 23 return; 24 } 25 string tmp = ((!)value).replace(">", ">").replace("<", "<").replace("'","'").replace(""","\""); 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("&", "&"); 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