Mercurial > pidgin
annotate src/xmlnode.c @ 12116:e75ef7aa913e
[gaim-migrate @ 14416]
" This patch implements a replacement for the queuing
system from 1.x. It also obsoletes a previous patch
[#1338873] I submitted to prioritize the unseen states
in gtk conversations.
The attached envelope.png is ripped from the
msgunread.png already included in gaim. It should be
dropped in the pixmaps directory (Makefile.am is
updated accordingly in this patch).
The two separate queuing preferences from 1.x, queuing
messages while away and queuing all new messages (from
docklet), are replaced with a single 3-way preference
for conversations. The new preference is "Hide new IM
conversations". This preference can be set to never,
away and always.
When a gtk conversation is created, it may be placed in
a hidden conversation window instead of being placed
normally. This decision is based upon the preference
and possibly the away state of the account the
conversation is being created for. This *will* effect
conversations the user explicitly requests to be
created, so in these cases the caller must be sure to
present the conversation to the user, using
gaim_gtkconv_present_conversation(). This is done
already in gtkdialogs.c which handles creating
conversations requested by the user from gaim proper
(menus, double-clicking on budy in blist, etc.).
The main advantage to not queuing messages is that the
conversations exist, the message is written to the
conversation (and logged if appropriate) and the unseen
state is set on the conversation. This means no
additional features are needed to track whether there
are queued messages or not, just use the unseen state
on conversations.
Since conversations may not be visible (messages
"queued"), gaim proper needs some notification that
there are messages waiting. I opted for a menutray icon
that shows up when an im conversation has an unseen
message. Clicking this icon will focus (and show if
hidden) the first conversation with an unseen message.
This is essentially the same behavior of the docklet in
cvs right now, except that the icon is only visible
when there is a conversation with an unread message.
The api that is added is flexible enough to allow
either the docklet or the new blist menutray icon to be
visible for conversations of any/all types and for
unseen messages >= any state. Currently they are set to
only IM conversations and only unseen states >= TEXT
(system messages and no log messages will not trigger
blinking the docklet or showing the blist tray icon),
but these could be made preferences relatively easily
in the future. Other plugins could probably benefit as
well: gaim_gtk_conversations_get_first_unseen().
There is probably some limit to comment size, so I'll
stop rambling now. If anyone has more
questions/comments, catch me in #gaim, here or on
gaim-devel."
committer: Tailor Script <tailor@pidgin.im>
author | Luke Schierer <lschiere@pidgin.im> |
---|---|
date | Wed, 16 Nov 2005 18:17:01 +0000 |
parents | da44f68fb4d2 |
children | 9679b615edb8 |
rev | line source |
---|---|
7131 | 1 /** |
2 * @file xmlnode.c XML DOM functions | |
3 * | |
4 * gaim | |
5 * | |
8046 | 6 * Gaim is the legal property of its developers, whose names are too numerous |
7 * to list here. Please refer to the COPYRIGHT file distributed with this | |
8 * source distribution. | |
7131 | 9 * |
10 * This program is free software; you can redistribute it and/or modify | |
11 * it under the terms of the GNU General Public License as published by | |
12 * the Free Software Foundation; either version 2 of the License, or | |
13 * (at your option) any later version. | |
14 * | |
15 * This program is distributed in the hope that it will be useful, | |
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
18 * GNU General Public License for more details. | |
19 * | |
20 * You should have received a copy of the GNU General Public License | |
21 * along with this program; if not, write to the Free Software | |
22 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | |
23 */ | |
24 | |
25 /* A lot of this code at least resembles the code in libxode, but since | |
26 * libxode uses memory pools that we simply have no need for, I decided to | |
27 * write my own stuff. Also, re-writing this lets me be as lightweight | |
28 * as I want to be. Thank you libxode for giving me a good starting point */ | |
29 | |
30 #include "internal.h" | |
31 | |
32 #include <string.h> | |
33 #include <glib.h> | |
34 | |
35 #include "xmlnode.h" | |
36 | |
12041
da44f68fb4d2
[gaim-migrate @ 14334]
Daniel Atallah <daniel.atallah@gmail.com>
parents:
11705
diff
changeset
|
37 #ifdef _WIN32 |
da44f68fb4d2
[gaim-migrate @ 14334]
Daniel Atallah <daniel.atallah@gmail.com>
parents:
11705
diff
changeset
|
38 # define NEWLINE_S "\r\n" |
da44f68fb4d2
[gaim-migrate @ 14334]
Daniel Atallah <daniel.atallah@gmail.com>
parents:
11705
diff
changeset
|
39 #else |
da44f68fb4d2
[gaim-migrate @ 14334]
Daniel Atallah <daniel.atallah@gmail.com>
parents:
11705
diff
changeset
|
40 # define NEWLINE_S "\n" |
da44f68fb4d2
[gaim-migrate @ 14334]
Daniel Atallah <daniel.atallah@gmail.com>
parents:
11705
diff
changeset
|
41 #endif |
da44f68fb4d2
[gaim-migrate @ 14334]
Daniel Atallah <daniel.atallah@gmail.com>
parents:
11705
diff
changeset
|
42 |
7131 | 43 static xmlnode* |
8135 | 44 new_node(const char *name, XMLNodeType type) |
7131 | 45 { |
46 xmlnode *node = g_new0(xmlnode, 1); | |
10423 | 47 |
7131 | 48 if(name) |
49 node->name = g_strdup(name); | |
50 node->type = type; | |
51 | |
52 return node; | |
53 } | |
54 | |
55 xmlnode* | |
56 xmlnode_new(const char *name) | |
57 { | |
58 g_return_val_if_fail(name != NULL, NULL); | |
59 | |
8135 | 60 return new_node(name, XMLNODE_TYPE_TAG); |
7131 | 61 } |
62 | |
10423 | 63 xmlnode * |
64 xmlnode_new_child(xmlnode *parent, const char *name) | |
7131 | 65 { |
66 xmlnode *node; | |
67 | |
68 g_return_val_if_fail(parent != NULL, NULL); | |
69 g_return_val_if_fail(name != NULL, NULL); | |
70 | |
8135 | 71 node = new_node(name, XMLNODE_TYPE_TAG); |
7131 | 72 |
73 xmlnode_insert_child(parent, node); | |
74 | |
75 return node; | |
76 } | |
77 | |
78 void | |
79 xmlnode_insert_child(xmlnode *parent, xmlnode *child) | |
80 { | |
81 g_return_if_fail(parent != NULL); | |
82 g_return_if_fail(child != NULL); | |
83 | |
84 child->parent = parent; | |
85 | |
86 if(parent->child) { | |
87 xmlnode *x; | |
88 for(x = parent->child; x->next; x = x->next); | |
89 x->next = child; | |
90 } else { | |
91 parent->child = child; | |
92 } | |
93 } | |
94 | |
95 void | |
10848 | 96 xmlnode_insert_data(xmlnode *node, const char *data, gssize size) |
7131 | 97 { |
10415 | 98 xmlnode *child; |
10848 | 99 gsize real_size; |
7131 | 100 |
10415 | 101 g_return_if_fail(node != NULL); |
7131 | 102 g_return_if_fail(data != NULL); |
103 g_return_if_fail(size != 0); | |
104 | |
105 real_size = size == -1 ? strlen(data) : size; | |
106 | |
10415 | 107 child = new_node(NULL, XMLNODE_TYPE_DATA); |
7131 | 108 |
10415 | 109 child->data = g_memdup(data, real_size); |
110 child->data_sz = real_size; | |
7131 | 111 |
10415 | 112 xmlnode_insert_child(node, child); |
7131 | 113 } |
114 | |
115 void | |
116 xmlnode_remove_attrib(xmlnode *node, const char *attr) | |
117 { | |
118 xmlnode *attr_node, *sibling = NULL; | |
119 | |
120 g_return_if_fail(node != NULL); | |
121 g_return_if_fail(attr != NULL); | |
122 | |
123 for(attr_node = node->child; attr_node; attr_node = attr_node->next) | |
124 { | |
8135 | 125 if(attr_node->type == XMLNODE_TYPE_ATTRIB && |
7131 | 126 !strcmp(attr_node->name, attr)) { |
127 if(node->child == attr_node) { | |
128 node->child = attr_node->next; | |
129 } else { | |
130 sibling->next = attr_node->next; | |
131 } | |
132 xmlnode_free(attr_node); | |
133 return; | |
134 } | |
135 sibling = attr_node; | |
136 } | |
137 } | |
138 | |
139 void | |
140 xmlnode_set_attrib(xmlnode *node, const char *attr, const char *value) | |
141 { | |
142 xmlnode *attrib_node; | |
143 | |
144 g_return_if_fail(node != NULL); | |
145 g_return_if_fail(attr != NULL); | |
146 g_return_if_fail(value != NULL); | |
147 | |
148 xmlnode_remove_attrib(node, attr); | |
149 | |
8135 | 150 attrib_node = new_node(attr, XMLNODE_TYPE_ATTRIB); |
7131 | 151 |
152 attrib_node->data = g_strdup(value); | |
153 | |
154 xmlnode_insert_child(node, attrib_node); | |
155 } | |
156 | |
10425 | 157 const char * |
7131 | 158 xmlnode_get_attrib(xmlnode *node, const char *attr) |
159 { | |
160 xmlnode *x; | |
161 | |
162 g_return_val_if_fail(node != NULL, NULL); | |
163 | |
164 for(x = node->child; x; x = x->next) { | |
8135 | 165 if(x->type == XMLNODE_TYPE_ATTRIB && !strcmp(attr, x->name)) { |
7131 | 166 return x->data; |
167 } | |
168 } | |
169 | |
170 return NULL; | |
171 } | |
172 | |
10423 | 173 void |
174 xmlnode_free(xmlnode *node) | |
7131 | 175 { |
176 xmlnode *x, *y; | |
177 | |
178 g_return_if_fail(node != NULL); | |
179 | |
180 x = node->child; | |
181 while(x) { | |
182 y = x->next; | |
183 xmlnode_free(x); | |
184 x = y; | |
185 } | |
186 | |
187 if(node->name) | |
188 g_free(node->name); | |
189 if(node->data) | |
190 g_free(node->data); | |
191 g_free(node); | |
192 } | |
193 | |
194 xmlnode* | |
10736 | 195 xmlnode_get_child(const xmlnode *parent, const char *name) |
196 { | |
197 return xmlnode_get_child_with_namespace(parent, name, NULL); | |
198 } | |
199 | |
200 xmlnode * | |
201 xmlnode_get_child_with_namespace(const xmlnode *parent, const char *name, const char *ns) | |
7131 | 202 { |
203 xmlnode *x, *ret = NULL; | |
204 char **names; | |
205 char *parent_name, *child_name; | |
206 | |
207 g_return_val_if_fail(parent != NULL, NULL); | |
208 | |
209 names = g_strsplit(name, "/", 2); | |
210 parent_name = names[0]; | |
211 child_name = names[1]; | |
212 | |
213 for(x = parent->child; x; x = x->next) { | |
8262 | 214 const char *xmlns = NULL; |
215 if(ns) | |
216 xmlns = xmlnode_get_attrib(x, "xmlns"); | |
217 | |
218 if(x->type == XMLNODE_TYPE_TAG && name && !strcmp(parent_name, x->name) | |
219 && (!ns || (xmlns && !strcmp(ns, xmlns)))) { | |
7131 | 220 ret = x; |
221 break; | |
222 } | |
223 } | |
224 | |
225 if(child_name && ret) | |
8262 | 226 ret = xmlnode_get_child(ret, child_name); |
7131 | 227 |
228 g_strfreev(names); | |
229 return ret; | |
230 } | |
231 | |
232 char * | |
233 xmlnode_get_data(xmlnode *node) | |
234 { | |
235 GString *str = NULL; | |
236 xmlnode *c; | |
237 | |
238 g_return_val_if_fail(node != NULL, NULL); | |
239 | |
240 for(c = node->child; c; c = c->next) { | |
8135 | 241 if(c->type == XMLNODE_TYPE_DATA) { |
7131 | 242 if(!str) |
243 str = g_string_new(""); | |
244 str = g_string_append_len(str, c->data, c->data_sz); | |
245 } | |
246 } | |
247 | |
10331 | 248 if (str == NULL) |
249 return NULL; | |
7131 | 250 |
10331 | 251 return g_string_free(str, FALSE); |
7131 | 252 } |
253 | |
10425 | 254 static char * |
10423 | 255 xmlnode_to_str_helper(xmlnode *node, int *len, gboolean formatting, int depth) |
7131 | 256 { |
257 GString *text = g_string_new(""); | |
258 xmlnode *c; | |
9837 | 259 char *node_name, *esc, *esc2, *tab = NULL; |
9838 | 260 gboolean need_end = FALSE, pretty = formatting; |
9837 | 261 |
262 if(pretty && depth) { | |
263 tab = g_strnfill(depth, '\t'); | |
264 text = g_string_append(text, tab); | |
265 } | |
7131 | 266 |
267 node_name = g_markup_escape_text(node->name, -1); | |
268 g_string_append_printf(text, "<%s", node_name); | |
269 | |
270 for(c = node->child; c; c = c->next) | |
271 { | |
8135 | 272 if(c->type == XMLNODE_TYPE_ATTRIB) { |
7131 | 273 esc = g_markup_escape_text(c->name, -1); |
274 esc2 = g_markup_escape_text(c->data, -1); | |
275 g_string_append_printf(text, " %s='%s'", esc, esc2); | |
276 g_free(esc); | |
277 g_free(esc2); | |
8135 | 278 } else if(c->type == XMLNODE_TYPE_TAG || c->type == XMLNODE_TYPE_DATA) { |
9837 | 279 if(c->type == XMLNODE_TYPE_DATA) |
9838 | 280 pretty = FALSE; |
7131 | 281 need_end = TRUE; |
282 } | |
283 } | |
284 | |
285 if(need_end) { | |
12041
da44f68fb4d2
[gaim-migrate @ 14334]
Daniel Atallah <daniel.atallah@gmail.com>
parents:
11705
diff
changeset
|
286 g_string_append_printf(text, ">%s", pretty ? NEWLINE_S : ""); |
7131 | 287 |
288 for(c = node->child; c; c = c->next) | |
289 { | |
8135 | 290 if(c->type == XMLNODE_TYPE_TAG) { |
7642 | 291 int esc_len; |
9838 | 292 esc = xmlnode_to_str_helper(c, &esc_len, pretty, depth+1); |
7642 | 293 text = g_string_append_len(text, esc, esc_len); |
7131 | 294 g_free(esc); |
8135 | 295 } else if(c->type == XMLNODE_TYPE_DATA) { |
7131 | 296 esc = g_markup_escape_text(c->data, c->data_sz); |
7642 | 297 text = g_string_append(text, esc); |
7131 | 298 g_free(esc); |
299 } | |
300 } | |
301 | |
9838 | 302 if(tab && pretty) |
9837 | 303 text = g_string_append(text, tab); |
12041
da44f68fb4d2
[gaim-migrate @ 14334]
Daniel Atallah <daniel.atallah@gmail.com>
parents:
11705
diff
changeset
|
304 g_string_append_printf(text, "</%s>%s", node_name, formatting ? NEWLINE_S : ""); |
7131 | 305 } else { |
12041
da44f68fb4d2
[gaim-migrate @ 14334]
Daniel Atallah <daniel.atallah@gmail.com>
parents:
11705
diff
changeset
|
306 g_string_append_printf(text, "/>%s", formatting ? NEWLINE_S : ""); |
7131 | 307 } |
308 | |
309 g_free(node_name); | |
310 | |
9837 | 311 if(tab) |
312 g_free(tab); | |
313 | |
7642 | 314 if(len) |
315 *len = text->len; | |
10331 | 316 |
317 return g_string_free(text, FALSE); | |
7131 | 318 } |
319 | |
10425 | 320 char * |
10423 | 321 xmlnode_to_str(xmlnode *node, int *len) |
322 { | |
9837 | 323 return xmlnode_to_str_helper(node, len, FALSE, 0); |
324 } | |
325 | |
10425 | 326 char * |
10423 | 327 xmlnode_to_formatted_str(xmlnode *node, int *len) |
328 { | |
10425 | 329 char *xml, *xml_with_declaration; |
10415 | 330 |
331 xml = xmlnode_to_str_helper(node, len, TRUE, 0); | |
332 xml_with_declaration = | |
12041
da44f68fb4d2
[gaim-migrate @ 14334]
Daniel Atallah <daniel.atallah@gmail.com>
parents:
11705
diff
changeset
|
333 g_strdup_printf("<?xml version='1.0' encoding='UTF-8' ?>" NEWLINE_S NEWLINE_S "%s", xml); |
10415 | 334 g_free(xml); |
335 | |
336 return xml_with_declaration; | |
9837 | 337 } |
338 | |
7131 | 339 struct _xmlnode_parser_data { |
340 xmlnode *current; | |
341 }; | |
342 | |
343 static void | |
344 xmlnode_parser_element_start(GMarkupParseContext *context, | |
345 const char *element_name, const char **attrib_names, | |
346 const char **attrib_values, gpointer user_data, GError **error) | |
347 { | |
348 struct _xmlnode_parser_data *xpd = user_data; | |
349 xmlnode *node; | |
350 int i; | |
351 | |
352 if(!element_name) { | |
353 return; | |
354 } else { | |
355 if(xpd->current) | |
356 node = xmlnode_new_child(xpd->current, element_name); | |
357 else | |
358 node = xmlnode_new(element_name); | |
359 | |
360 for(i=0; attrib_names[i]; i++) | |
361 xmlnode_set_attrib(node, attrib_names[i], attrib_values[i]); | |
362 | |
363 xpd->current = node; | |
364 } | |
365 } | |
366 | |
367 static void | |
368 xmlnode_parser_element_end(GMarkupParseContext *context, | |
369 const char *element_name, gpointer user_data, GError **error) | |
370 { | |
371 struct _xmlnode_parser_data *xpd = user_data; | |
372 | |
373 if(!element_name || !xpd->current) | |
374 return; | |
375 | |
376 if(xpd->current->parent) { | |
377 if(!strcmp(xpd->current->name, element_name)) | |
378 xpd->current = xpd->current->parent; | |
379 } | |
380 } | |
381 | |
382 static void | |
383 xmlnode_parser_element_text(GMarkupParseContext *context, const char *text, | |
384 gsize text_len, gpointer user_data, GError **error) | |
385 { | |
386 struct _xmlnode_parser_data *xpd = user_data; | |
387 | |
388 if(!xpd->current) | |
389 return; | |
390 | |
391 if(!text || !text_len) | |
392 return; | |
393 | |
394 xmlnode_insert_data(xpd->current, text, text_len); | |
395 } | |
396 | |
397 static GMarkupParser xmlnode_parser = { | |
398 xmlnode_parser_element_start, | |
399 xmlnode_parser_element_end, | |
400 xmlnode_parser_element_text, | |
401 NULL, | |
402 NULL | |
403 }; | |
404 | |
405 | |
10423 | 406 xmlnode * |
10848 | 407 xmlnode_from_str(const char *str, gssize size) |
7131 | 408 { |
11390 | 409 struct _xmlnode_parser_data *xpd; |
7131 | 410 xmlnode *ret; |
411 GMarkupParseContext *context; | |
11390 | 412 gsize real_size; |
7131 | 413 |
11390 | 414 g_return_val_if_fail(str != NULL, NULL); |
415 | |
11705
0906a3e9626c
[gaim-migrate @ 13996]
Richard Laager <rlaager@wiktel.com>
parents:
11390
diff
changeset
|
416 real_size = size < 0 ? strlen(str) : size; |
11390 | 417 xpd = g_new0(struct _xmlnode_parser_data, 1); |
7131 | 418 context = g_markup_parse_context_new(&xmlnode_parser, 0, xpd, NULL); |
419 | |
420 if(!g_markup_parse_context_parse(context, str, real_size, NULL)) { | |
421 while(xpd->current && xpd->current->parent) | |
422 xpd->current = xpd->current->parent; | |
423 if(xpd->current) | |
424 xmlnode_free(xpd->current); | |
425 xpd->current = NULL; | |
426 } | |
427 g_markup_parse_context_free(context); | |
428 | |
429 ret = xpd->current; | |
430 g_free(xpd); | |
431 return ret; | |
432 } | |
8135 | 433 |
10423 | 434 xmlnode * |
435 xmlnode_copy(xmlnode *src) | |
8135 | 436 { |
437 xmlnode *ret; | |
438 xmlnode *child; | |
439 xmlnode *sibling = NULL; | |
440 | |
441 if(!src) | |
442 return NULL; | |
443 | |
444 ret = new_node(src->name, src->type); | |
445 if(src->data) { | |
8167 | 446 if(src->data_sz) { |
447 ret->data = g_memdup(src->data, src->data_sz); | |
448 ret->data_sz = src->data_sz; | |
449 } else { | |
450 ret->data = g_strdup(src->data); | |
451 } | |
8135 | 452 } |
453 | |
454 for(child = src->child; child; child = child->next) { | |
455 if(sibling) { | |
456 sibling->next = xmlnode_copy(child); | |
457 sibling = sibling->next; | |
458 } else { | |
459 ret->child = xmlnode_copy(child); | |
460 sibling = ret->child; | |
461 } | |
462 sibling->parent = ret; | |
463 } | |
464 | |
465 return ret; | |
466 } | |
467 | |
10423 | 468 xmlnode * |
469 xmlnode_get_next_twin(xmlnode *node) | |
470 { | |
8135 | 471 xmlnode *sibling; |
8262 | 472 const char *ns = xmlnode_get_attrib(node, "xmlns"); |
8135 | 473 |
474 g_return_val_if_fail(node != NULL, NULL); | |
475 g_return_val_if_fail(node->type == XMLNODE_TYPE_TAG, NULL); | |
476 | |
477 for(sibling = node->next; sibling; sibling = sibling->next) { | |
8283 | 478 const char *xmlns = NULL; |
8262 | 479 if(ns) |
480 xmlns = xmlnode_get_attrib(sibling, "xmlns"); | |
481 | |
482 if(sibling->type == XMLNODE_TYPE_TAG && !strcmp(node->name, sibling->name) && | |
483 (!ns || (xmlns && !strcmp(ns, xmlns)))) | |
8135 | 484 return sibling; |
485 } | |
486 | |
487 return NULL; | |
488 } |