comparison libpurple/protocols/myspace/markup.c @ 25237:401f548e3544

propagate from branch 'im.pidgin.pidgin' (head df6eba32e5b6b34d7483cbfb7e9f2e4c836ac35f) to branch 'org.darkrain42.pidgin.buddy-add' (head 6831808999a270f8c1a128c7430a73d3dc0bfae2)
author Paul Aurich <paul@darkrain42.org>
date Sun, 21 Dec 2008 18:32:37 +0000
parents e12788365764
children c51fc4daf81b
comparison
equal deleted inserted replaced
25172:125cac3e24ee 25237:401f548e3544
19 19
20 #include "myspace.h" 20 #include "myspace.h"
21 21
22 typedef int (*MSIM_XMLNODE_CONVERT)(MsimSession *, xmlnode *, gchar **, gchar **); 22 typedef int (*MSIM_XMLNODE_CONVERT)(MsimSession *, xmlnode *, gchar **, gchar **);
23 23
24 /* Internal functions */
25
26 static guint msim_point_to_purple_size(MsimSession *session, guint point);
27 static guint msim_purple_size_to_point(MsimSession *session, guint size);
28 static guint msim_height_to_point(MsimSession *session, guint height);
29 static guint msim_point_to_height(MsimSession *session, guint point);
30
31 static int msim_markup_tag_to_html(MsimSession *, xmlnode *root, gchar **begin, gchar **end);
32 static int html_tag_to_msim_markup(MsimSession *, xmlnode *root, gchar **begin, gchar **end);
33 static gchar *msim_convert_xml(MsimSession *, const gchar *raw, MSIM_XMLNODE_CONVERT f);
34 static gchar *msim_convert_smileys_to_markup(gchar *before);
35 static double msim_round(double round);
36
37
38 /* Globals */ 24 /* Globals */
39 25
40 /* The names in in emoticon_names (for <i n=whatever>) map to corresponding 26 /* The names in in emoticon_names (for <i n=whatever>) map to corresponding
41 * entries in emoticon_symbols (for the ASCII representation of the emoticon). 27 * entries in emoticon_symbols (for the ASCII representation of the emoticon).
42 * 28 *
43 * Multiple emoticon symbols in Pidgin can map to one name. List the 29 * Multiple emoticon symbols in Pidgin can map to one name. List the
44 * canonical form, as inserted by the "Smile!" dialog, first. For example, 30 * canonical form, as inserted by the "Smile!" dialog, first. For example,
45 * :) comes before :-), because although both are recognized as 'happy', 31 * :) comes before :-), because although both are recognized as 'happy',
88 { "worried", ":[" }, 74 { "worried", ":[" },
89 { "kiss", ":x" }, 75 { "kiss", ":x" },
90 { NULL, NULL } 76 { NULL, NULL }
91 }; 77 };
92 78
93
94
95 /* Indexes of this array + 1 map HTML font size to scale of normal font size. * 79 /* Indexes of this array + 1 map HTML font size to scale of normal font size. *
96 * Based on _point_sizes from libpurple/gtkimhtml.c 80 * Based on _point_sizes from libpurple/gtkimhtml.c
97 * 1 2 3 4 5 6 7 */ 81 * 1 2 3 4 5 6 7 */
98 static gdouble _font_scale[] = { .85, .95, 1, 1.2, 1.44, 1.728, 2.0736 }; 82 static gdouble _font_scale[] = { .85, .95, 1, 1.2, 1.44, 1.728, 2.0736 };
99 83
100 #define MAX_FONT_SIZE 7 /* Purple maximum font size */ 84 /* Purple maximum font size. Equivalent to sizeof(_font_scale) / sizeof(_font_scale[0]) */
85 #define MAX_FONT_SIZE 7
86
101 #define POINTS_PER_INCH 72 /* How many pt's in an inch */ 87 #define POINTS_PER_INCH 72 /* How many pt's in an inch */
102 88
103 /* Text formatting bits for <f s=#> */ 89 /* Text formatting bits for <f s=#> */
104 #define MSIM_TEXT_BOLD 1 90 #define MSIM_TEXT_BOLD 1
105 #define MSIM_TEXT_ITALIC 2 91 #define MSIM_TEXT_ITALIC 2
106 #define MSIM_TEXT_UNDERLINE 4 92 #define MSIM_TEXT_UNDERLINE 4
107 93
108 /* Default baseline size of purple's fonts, in points. What is size 3 in points. 94 /* Default baseline size of purple's fonts, in points. What is size 3 in points.
109 * _font_scale specifies scaling factor relative to this point size. Note this 95 * _font_scale specifies scaling factor relative to this point size. Note this
110 * is only the default; it is configurable in account options. */ 96 * is only the default; it is configurable in account options. */
111 #define MSIM_BASE_FONT_POINT_SIZE 8 97 #define MSIM_BASE_FONT_POINT_SIZE 8
112 98
113 /* Default display's DPI. 96 is common but it can differ. Also configurable 99 /* Default display's DPI. 96 is common but it can differ. Also configurable
114 * in account options. */ 100 * in account options. */
115 #define MSIM_DEFAULT_DPI 96 101 #define MSIM_DEFAULT_DPI 96
116 102
117
118 /* round is part of C99, but sometimes is unavailable before then. 103 /* round is part of C99, but sometimes is unavailable before then.
119 * Based on http://forums.belution.com/en/cpp/000/050/13.shtml 104 * Based on http://forums.belution.com/en/cpp/000/050/13.shtml
120 */ 105 */
121 double msim_round(double value) 106 static double msim_round(double value)
122 { 107 {
123 if (value < 0) { 108 if (value < 0) {
124 return -(floor(-value + 0.5)); 109 return -(floor(-value + 0.5));
125 } else { 110 } else {
126 return floor( value + 0.5); 111 return floor( value + 0.5);
127 } 112 }
128 } 113 }
129 114
130 115 /** Convert typographical font point size to HTML font size.
131 /** Convert typographical font point size to HTML font size.
132 * Based on libpurple/gtkimhtml.c */ 116 * Based on libpurple/gtkimhtml.c */
133 static guint 117 static guint
134 msim_point_to_purple_size(MsimSession *session, guint point) 118 msim_point_to_purple_size(MsimSession *session, guint point)
135 { 119 {
136 guint size, this_point, base; 120 guint size, this_point, base;
137 gdouble scale; 121
138
139 base = purple_account_get_int(session->account, "base_font_size", MSIM_BASE_FONT_POINT_SIZE); 122 base = purple_account_get_int(session->account, "base_font_size", MSIM_BASE_FONT_POINT_SIZE);
140 123
141 for (size = 0; 124 for (size = 0; size < MAX_FONT_SIZE; ++size) {
142 size < sizeof(_font_scale) / sizeof(_font_scale[0]); 125 this_point = (guint)msim_round(base * _font_scale[size]);
143 ++size) {
144 scale = _font_scale[CLAMP(size, 1, MAX_FONT_SIZE) - 1];
145 this_point = (guint)msim_round(scale * base);
146 126
147 if (this_point >= point) { 127 if (this_point >= point) {
148 purple_debug_info("msim", "msim_point_to_purple_size: %d pt -> size=%d\n", 128 purple_debug_info("msim", "msim_point_to_purple_size: %d pt -> size=%d\n",
149 point, size); 129 point, size);
150 return size; 130 return size;
174 154
175 return point; 155 return point;
176 } 156 }
177 157
178 /** Convert a msim markup font pixel height to the more usual point size, for incoming messages. */ 158 /** Convert a msim markup font pixel height to the more usual point size, for incoming messages. */
179 static guint 159 static guint
180 msim_height_to_point(MsimSession *session, guint height) 160 msim_height_to_point(MsimSession *session, guint height)
181 { 161 {
182 guint dpi; 162 guint dpi;
183 163
184 dpi = purple_account_get_int(session->account, "dpi", MSIM_DEFAULT_DPI); 164 dpi = purple_account_get_int(session->account, "dpi", MSIM_DEFAULT_DPI);
199 179
200 return (guint)msim_round((dpi * 1. / POINTS_PER_INCH) * point); 180 return (guint)msim_round((dpi * 1. / POINTS_PER_INCH) * point);
201 } 181 }
202 182
203 /** Convert the msim markup <f> (font) tag into HTML. */ 183 /** Convert the msim markup <f> (font) tag into HTML. */
204 static void 184 static void
205 msim_markup_f_to_html(MsimSession *session, xmlnode *root, gchar **begin, gchar **end) 185 msim_markup_f_to_html(MsimSession *session, xmlnode *root, gchar **begin, gchar **end)
206 { 186 {
207 const gchar *face, *height_str, *decor_str; 187 const gchar *face, *height_str, *decor_str;
208 GString *gs_end, *gs_begin; 188 GString *gs_end, *gs_begin;
209 guint decor, height; 189 guint decor, height;
210 190
211 face = xmlnode_get_attrib(root, "f"); 191 face = xmlnode_get_attrib(root, "f");
212 height_str = xmlnode_get_attrib(root, "h"); 192 height_str = xmlnode_get_attrib(root, "h");
213 decor_str = xmlnode_get_attrib(root, "s"); 193 decor_str = xmlnode_get_attrib(root, "s");
214 194
215 if (height_str) { 195 /* Validate the font face, to avoid constructing invalid HTML later */
216 height = atol(height_str); 196 if (face != NULL && strchr(face, '\'') != NULL)
197 face = NULL;
198
199 height = height_str != NULL ? atol(height_str) : 12;
200 decor = decor_str != NULL ? atol(decor_str) : 0;
201
202 /*
203 * The HTML we're constructing here is a bit redudant. Ideally we
204 * would use only the font-family and font-size CSS span, but Pidgin
205 * doesn't support it (it's included for other UIs). For Pidgin we
206 * wrap the whole thing in an ugly font tag, and Pidgin will happily
207 * ignore the <span>.
208 */
209 gs_begin = g_string_new("");
210 if (height && !face) {
211 guint point_size = msim_height_to_point(session, height);
212 g_string_printf(gs_begin,
213 "<font size='%d'><span style='font-size: %dpt'>",
214 msim_point_to_purple_size(session, point_size),
215 point_size);
216 } else if (height && face) {
217 guint point_size = msim_height_to_point(session, height);
218 g_string_printf(gs_begin,
219 "<font face='%s' size='%d'><span style='font-family: %s; font-size: %dpt'>",
220 face, msim_point_to_purple_size(session, point_size),
221 face, point_size);
217 } else { 222 } else {
218 height = 12; 223 g_string_printf(gs_begin, "<font><span>");
219 } 224 }
220 225
221 if (decor_str) { 226 gs_end = g_string_new("</span></font>");
222 decor = atol(decor_str);
223 } else {
224 decor = 0;
225 }
226
227 gs_begin = g_string_new("");
228 /* TODO: get font size working */
229 if (height && !face) {
230 g_string_printf(gs_begin, "<font size='%d'>",
231 msim_point_to_purple_size(session, msim_height_to_point(session, height)));
232 } else if (height && face) {
233 g_string_printf(gs_begin, "<font face='%s' size='%d'>", face,
234 msim_point_to_purple_size(session, msim_height_to_point(session, height)));
235 } else {
236 g_string_printf(gs_begin, "<font>");
237 }
238
239 /* No support for font-size CSS? */
240 /* g_string_printf(gs_begin, "<span style='font-family: %s; font-size: %dpt'>", face,
241 msim_height_to_point(height)); */
242
243 gs_end = g_string_new("</font>");
244 227
245 if (decor & MSIM_TEXT_BOLD) { 228 if (decor & MSIM_TEXT_BOLD) {
246 g_string_append(gs_begin, "<b>"); 229 g_string_append(gs_begin, "<b>");
247 g_string_prepend(gs_end, "</b>"); 230 g_string_prepend(gs_end, "</b>");
248 } 231 }
255 if (decor & MSIM_TEXT_UNDERLINE) { 238 if (decor & MSIM_TEXT_UNDERLINE) {
256 g_string_append(gs_begin, "<u>"); 239 g_string_append(gs_begin, "<u>");
257 g_string_append(gs_end, "</u>"); 240 g_string_append(gs_end, "</u>");
258 } 241 }
259 242
260
261 *begin = g_string_free(gs_begin, FALSE); 243 *begin = g_string_free(gs_begin, FALSE);
262 *end = g_string_free(gs_end, FALSE); 244 *end = g_string_free(gs_end, FALSE);
263 } 245 }
264 246
265 /** Convert a msim markup color to a color suitable for libpurple. 247 /** Convert a msim markup color to a color suitable for libpurple.
266 * 248 *
267 * @param msim Either a color name, or an rgb(x,y,z) code. 249 * @param msim Either a color name, or an rgb(x,y,z) code.
268 * 250 *
269 * @return A new string, either a color name or #rrggbb code. Must g_free(). 251 * @return A new string, either a color name or #rrggbb code. Must g_free().
270 */ 252 */
271 static char * 253 static char *
272 msim_color_to_purple(const char *msim) 254 msim_color_to_purple(const char *msim)
273 { 255 {
274 guint red, green, blue; 256 guint red, green, blue;
275 257
285 267
286 return g_strdup_printf("#%.2x%.2x%.2x", red, green, blue); 268 return g_strdup_printf("#%.2x%.2x%.2x", red, green, blue);
287 } 269 }
288 270
289 /** Convert the msim markup <a> (anchor) tag into HTML. */ 271 /** Convert the msim markup <a> (anchor) tag into HTML. */
290 static void 272 static void
291 msim_markup_a_to_html(MsimSession *session, xmlnode *root, gchar **begin, gchar **end) 273 msim_markup_a_to_html(MsimSession *session, xmlnode *root, gchar **begin, gchar **end)
292 { 274 {
293 const gchar *href; 275 const gchar *href;
294 276
295 href = xmlnode_get_attrib(root, "h"); 277 href = xmlnode_get_attrib(root, "h");
300 *begin = g_strdup_printf("<a href=\"%s\">%s", href, href); 282 *begin = g_strdup_printf("<a href=\"%s\">%s", href, href);
301 *end = g_strdup("</a>"); 283 *end = g_strdup("</a>");
302 } 284 }
303 285
304 /** Convert the msim markup <p> (paragraph) tag into HTML. */ 286 /** Convert the msim markup <p> (paragraph) tag into HTML. */
305 static void 287 static void
306 msim_markup_p_to_html(MsimSession *session, xmlnode *root, gchar **begin, gchar **end) 288 msim_markup_p_to_html(MsimSession *session, xmlnode *root, gchar **begin, gchar **end)
307 { 289 {
308 /* Just pass through unchanged. 290 /* Just pass through unchanged.
309 * 291 *
310 * Note: attributes currently aren't passed, if there are any. */ 292 * Note: attributes currently aren't passed, if there are any. */
311 *begin = g_strdup("<p>"); 293 *begin = g_strdup("<p>");
312 *end = g_strdup("</p>"); 294 *end = g_strdup("</p>");
313 } 295 }
314 296
315 /** Convert the msim markup <c> tag (text color) into HTML. TODO: Test */ 297 /**
316 static void 298 * Convert the msim markup <c> tag (text color) into HTML.
299 */
300 static void
317 msim_markup_c_to_html(MsimSession *session, xmlnode *root, gchar **begin, gchar **end) 301 msim_markup_c_to_html(MsimSession *session, xmlnode *root, gchar **begin, gchar **end)
318 { 302 {
319 const gchar *color; 303 const gchar *color;
320 gchar *purple_color; 304 gchar *purple_color;
321 305
328 return; 312 return;
329 } 313 }
330 314
331 purple_color = msim_color_to_purple(color); 315 purple_color = msim_color_to_purple(color);
332 316
333 *begin = g_strdup_printf("<font color='%s'>", purple_color); 317 #ifdef USE_CSS_FORMATTING
318 *begin = g_strdup_printf("<span style='color: %s'>", purple_color);
319 *end = g_strdup("</span>");
320 #else
321 *begin = g_strdup_printf("<font color='%s'>", purple_color);
322 *end = g_strdup("</font>");
323 #endif
334 324
335 g_free(purple_color); 325 g_free(purple_color);
336 326 }
337 /* *begin = g_strdup_printf("<span style='color: %s'>", color); */ 327
338 *end = g_strdup("</font>"); 328 /**
339 } 329 * Convert the msim markup <b> tag (background color) into HTML.
340 330 */
341 /** Convert the msim markup <b> tag (background color) into HTML. TODO: Test */ 331 static void
342 static void
343 msim_markup_b_to_html(MsimSession *session, xmlnode *root, gchar **begin, gchar **end) 332 msim_markup_b_to_html(MsimSession *session, xmlnode *root, gchar **begin, gchar **end)
344 { 333 {
345 const gchar *color; 334 const gchar *color;
346 gchar *purple_color; 335 gchar *purple_color;
347 336
354 return; 343 return;
355 } 344 }
356 345
357 purple_color = msim_color_to_purple(color); 346 purple_color = msim_color_to_purple(color);
358 347
359 /* TODO: find out how to set background color. */ 348 #ifdef USE_CSS_FORMATTING
360 *begin = g_strdup_printf("<span style='background-color: %s'>", 349 *begin = g_strdup_printf("<span style='background-color: %s'>", purple_color);
361 purple_color); 350 *end = g_strdup("</span>");
351 #else
352 *begin = g_strdup_printf("<body bgcolor='%s'>", purple_color);
353 *end = g_strdup("</body>");
354 #endif
355
362 g_free(purple_color); 356 g_free(purple_color);
363
364 *end = g_strdup("</p>");
365 } 357 }
366 358
367 /** Convert the msim markup <i> tag (emoticon image) into HTML. */ 359 /** Convert the msim markup <i> tag (emoticon image) into HTML. */
368 static void 360 static void
369 msim_markup_i_to_html(MsimSession *session, xmlnode *root, gchar **begin, gchar **end) 361 msim_markup_i_to_html(MsimSession *session, xmlnode *root, gchar **begin, gchar **end)
370 { 362 {
371 const gchar *name; 363 const gchar *name;
372 guint i; 364 guint i;
373 struct MSIM_EMOTICON *emote; 365 struct MSIM_EMOTICON *emote;
394 *begin = g_strdup_printf("**%s**", name); 386 *begin = g_strdup_printf("**%s**", name);
395 *end = g_strdup(""); 387 *end = g_strdup("");
396 } 388 }
397 389
398 /** Convert an individual msim markup tag to HTML. */ 390 /** Convert an individual msim markup tag to HTML. */
399 static int 391 static int
400 msim_markup_tag_to_html(MsimSession *session, xmlnode *root, gchar **begin, 392 msim_markup_tag_to_html(MsimSession *session, xmlnode *root, gchar **begin,
401 gchar **end) 393 gchar **end)
402 { 394 {
403 g_return_val_if_fail(root != NULL, 0); 395 g_return_val_if_fail(root != NULL, 0);
404 396
405 if (g_str_equal(root->name, "f")) { 397 if (g_str_equal(root->name, "f")) {
414 msim_markup_b_to_html(session, root, begin, end); 406 msim_markup_b_to_html(session, root, begin, end);
415 } else if (g_str_equal(root->name, "i")) { 407 } else if (g_str_equal(root->name, "i")) {
416 msim_markup_i_to_html(session, root, begin, end); 408 msim_markup_i_to_html(session, root, begin, end);
417 } else { 409 } else {
418 purple_debug_info("msim", "msim_markup_tag_to_html: " 410 purple_debug_info("msim", "msim_markup_tag_to_html: "
419 "unknown tag name=%s, ignoring", 411 "unknown tag name=%s, ignoring\n",
420 root->name ? root->name : "(NULL)"); 412 root->name ? root->name : "(NULL)");
421 *begin = g_strdup(""); 413 *begin = g_strdup("");
422 *end = g_strdup(""); 414 *end = g_strdup("");
423 } 415 }
424 return 0; 416 return 0;
425 } 417 }
426 418
427 /** Convert an individual HTML tag to msim markup. */ 419 /** Convert an individual HTML tag to msim markup. */
428 static int 420 static int
429 html_tag_to_msim_markup(MsimSession *session, xmlnode *root, gchar **begin, 421 html_tag_to_msim_markup(MsimSession *session, xmlnode *root, gchar **begin,
430 gchar **end) 422 gchar **end)
431 { 423 {
432 int ret = 0; 424 int ret = 0;
433 425
434 if (!purple_utf8_strcasecmp(root->name, "root") || 426 if (!purple_utf8_strcasecmp(root->name, "root") ||
435 !purple_utf8_strcasecmp(root->name, "html")) { 427 !purple_utf8_strcasecmp(root->name, "html")) {
436 *begin = g_strdup(""); 428 *begin = g_strdup("");
437 *end = g_strdup(""); 429 *end = g_strdup("");
438 /* TODO: Coalesce nested tags into one <f> tag! 430 /* TODO: Coalesce nested tags into one <f> tag!
439 * Currently, the 's' value will be overwritten when b/i/u is nested 431 * Currently, the 's' value will be overwritten when b/i/u is nested
440 * within another one, and only the inner-most formatting will be 432 * within another one, and only the inner-most formatting will be
441 * applied to the text. */ 433 * applied to the text. */
442 } else if (!purple_utf8_strcasecmp(root->name, "b")) { 434 } else if (!purple_utf8_strcasecmp(root->name, "b")) {
443 if (root->child->type == XMLNODE_TYPE_DATA) { 435 if (root->child->type == XMLNODE_TYPE_DATA) {
444 *begin = g_strdup_printf("<f s='%d'>", MSIM_TEXT_BOLD); 436 *begin = g_strdup_printf("<f s='%d'>", MSIM_TEXT_BOLD);
445 *end = g_strdup("</f>"); 437 *end = g_strdup("</f>");
513 g_free(link_text); 505 g_free(link_text);
514 root->child = NULL; 506 root->child = NULL;
515 507
516 *end = g_strdup(""); 508 *end = g_strdup("");
517 } else if (!purple_utf8_strcasecmp(root->name, "font")) { 509 } else if (!purple_utf8_strcasecmp(root->name, "font")) {
510 GString *tmpbegin, *tmpend;
518 const gchar *size; 511 const gchar *size;
519 const gchar *face; 512 const gchar *face;
513 const gchar *color;
520 514
521 size = xmlnode_get_attrib(root, "size"); 515 size = xmlnode_get_attrib(root, "size");
522 face = xmlnode_get_attrib(root, "face"); 516 face = xmlnode_get_attrib(root, "face");
523 517 color = xmlnode_get_attrib(root, "color");
524 if (face && size) { 518
525 *begin = g_strdup_printf("<f f='%s' h='%d'>", face, 519 tmpbegin = g_string_new("<f");
526 msim_point_to_height(session, 520 tmpend = g_string_new("</f>");
527 msim_purple_size_to_point(session, atoi(size)))); 521
528 } else if (face) { 522 if (face != NULL)
529 *begin = g_strdup_printf("<f f='%s'>", face); 523 g_string_append_printf(tmpbegin, "f='%s'", face);
530 } else if (size) { 524
531 *begin = g_strdup_printf("<f h='%d'>", 525 if (size != NULL)
526 g_string_append_printf(tmpbegin, "h='%d'",
532 msim_point_to_height(session, 527 msim_point_to_height(session,
533 msim_purple_size_to_point(session, atoi(size)))); 528 msim_purple_size_to_point(session, atoi(size))));
534 } else { 529
535 *begin = g_strdup("<f>"); 530 /* Close the <f> tag */
536 } 531 g_string_append(tmpbegin, ">");
537 532
538 *end = g_strdup("</f>"); 533 if (color != NULL) {
539 534 g_string_append_printf(tmpbegin, "<c v='%s'>", color);
540 /* TODO: color (bg uses <body>), emoticons */ 535 g_string_prepend(tmpend, "</c>");
536 }
537
538 *begin = g_string_free(tmpbegin, FALSE);
539 *end = g_string_free(tmpend, FALSE);
540
541 } else if (!purple_utf8_strcasecmp(root->name, "body")) {
542 const gchar *bgcolor;
543
544 bgcolor = xmlnode_get_attrib(root, "bgcolor");
545
546 if (bgcolor != NULL) {
547 *begin = g_strdup_printf("<b v='%s'>", bgcolor);
548 *end = g_strdup("</b>");
549 }
550
541 } else { 551 } else {
542 gchar *err; 552 gchar *err;
543 553
544 #ifdef MSIM_MARKUP_SHOW_UNKNOWN_TAGS 554 #ifdef MSIM_MARKUP_SHOW_UNKNOWN_TAGS
545 *begin = g_strdup_printf("[%s]", root->name); 555 *begin = g_strdup_printf("[%s]", root->name);
548 *begin = g_strdup(""); 558 *begin = g_strdup("");
549 *end = g_strdup(""); 559 *end = g_strdup("");
550 #endif 560 #endif
551 561
552 err = g_strdup_printf("html_tag_to_msim_markup: unrecognized " 562 err = g_strdup_printf("html_tag_to_msim_markup: unrecognized "
553 "HTML tag %s was sent by the IM client; ignoring", 563 "HTML tag %s was sent by the IM client; ignoring",
554 root->name ? root->name : "(NULL)"); 564 root->name ? root->name : "(NULL)");
555 msim_unrecognized(NULL, NULL, err); 565 msim_unrecognized(NULL, NULL, err);
556 g_free(err); 566 g_free(err);
557 } 567 }
558 return ret; 568 return ret;
562 * 572 *
563 * @param f Function to convert tags. 573 * @param f Function to convert tags.
564 * 574 *
565 * @return An HTML string. Caller frees. 575 * @return An HTML string. Caller frees.
566 */ 576 */
567 static gchar * 577 static void
568 msim_convert_xmlnode(MsimSession *session, xmlnode *root, MSIM_XMLNODE_CONVERT f, int nodes_processed) 578 msim_convert_xmlnode(MsimSession *session, GString *out, xmlnode *root, MSIM_XMLNODE_CONVERT f, int nodes_processed)
569 { 579 {
570 xmlnode *node; 580 xmlnode *node;
571 gchar *begin, *inner, *end; 581 gchar *begin, *inner, *end, *tmp;
572 GString *final;
573 int descended = nodes_processed; 582 int descended = nodes_processed;
574 583
575 if (!root || !root->name) { 584 if (!root || !root->name)
576 return g_strdup(""); 585 return;
577 }
578 586
579 purple_debug_info("msim", "msim_convert_xmlnode: got root=%s\n", 587 purple_debug_info("msim", "msim_convert_xmlnode: got root=%s\n",
580 root->name); 588 root->name);
581 589
582 begin = inner = end = NULL; 590 begin = inner = end = NULL;
583 591
584 final = g_string_new("");
585
586 if (descended == 0) /* We've not formatted this yet.. :) */ 592 if (descended == 0) /* We've not formatted this yet.. :) */
587 descended = f(session, root, &begin, &end); /* Get the value that our format function has already descended for us */ 593 descended = f(session, root, &begin, &end); /* Get the value that our format function has already descended for us */
588 594
589 g_string_append(final, begin); 595 g_string_append(out, begin);
596 g_free(begin);
590 597
591 /* Loop over all child nodes. */ 598 /* Loop over all child nodes. */
592 for (node = root->child; node != NULL; node = node->next) { 599 for (node = root->child; node != NULL; node = node->next) {
593 switch (node->type) { 600 switch (node->type) {
594 case XMLNODE_TYPE_ATTRIB: 601 case XMLNODE_TYPE_ATTRIB:
595 /* Attributes handled above. */ 602 /* Attributes handled above. */
596 break; 603 break;
597 604
598 case XMLNODE_TYPE_TAG: 605 case XMLNODE_TYPE_TAG:
599 /* A tag or tag with attributes. Recursively descend. */ 606 /* A tag or tag with attributes. Recursively descend. */
600 inner = msim_convert_xmlnode(session, node, f, descended); 607 msim_convert_xmlnode(session, out, node, f, descended);
601 g_return_val_if_fail(inner != NULL, NULL); 608
602 609 purple_debug_info("msim", " ** node name=%s\n",
603 purple_debug_info("msim", " ** node name=%s\n", 610 node->name ? node->name : "(NULL)");
604 (node && node->name) ? node->name : "(NULL)");
605 break; 611 break;
606 612
607 case XMLNODE_TYPE_DATA: 613 case XMLNODE_TYPE_DATA:
608 /* Literal text. */ 614 /* Literal text. */
609 inner = g_strndup(node->data, node->data_sz); 615 /*
610 purple_debug_info("msim", " ** node data=%s\n", 616 * TODO: Why is it necessary to escape here? I thought
611 inner ? inner : "(NULL)"); 617 * node->data was already escaped?
618 */
619 tmp = g_markup_escape_text(node->data, node->data_sz);
620 g_string_append(out, tmp);
621 g_free(tmp);
612 break; 622 break;
613 623
614 default: 624 default:
615 purple_debug_info("msim", 625 purple_debug_warning("msim",
616 "msim_convert_xmlnode: strange node\n"); 626 "msim_convert_xmlnode: unknown node type\n");
617 }
618
619 if (inner) {
620 g_string_append(final, inner);
621 g_free(inner);
622 inner = NULL;
623 } 627 }
624 } 628 }
625 629
626 /* TODO: Note that msim counts each piece of text enclosed by <f> as 630 /* TODO: Note that msim counts each piece of text enclosed by <f> as
627 * a paragraph and will display each on its own line. You actually have 631 * a paragraph and will display each on its own line. You actually have
628 * to _nest_ <f> tags to intersperse different text in one paragraph! 632 * to _nest_ <f> tags to intersperse different text in one paragraph!
629 * Comment out this line below to see. */ 633 * Comment out this line below to see. */
630 g_string_append(final, end); 634 g_string_append(out, end);
631
632 g_free(begin);
633 g_free(end); 635 g_free(end);
634
635 purple_debug_info("msim", "msim_markup_xmlnode_to_gtkhtml: RETURNING %s\n",
636 (final && final->str) ? final->str : "(NULL)");
637
638 return g_string_free(final, FALSE);
639 } 636 }
640 637
641 /** Convert XML to something based on MSIM_XMLNODE_CONVERT. */ 638 /** Convert XML to something based on MSIM_XMLNODE_CONVERT. */
642 static gchar * 639 static gchar *
643 msim_convert_xml(MsimSession *session, const gchar *raw, MSIM_XMLNODE_CONVERT f) 640 msim_convert_xml(MsimSession *session, const gchar *raw, MSIM_XMLNODE_CONVERT f)
644 { 641 {
645 xmlnode *root; 642 xmlnode *root;
646 gchar *str; 643 GString *str;
647 gchar *enclosed_raw; 644 gchar *enclosed_raw;
648 645
649 g_return_val_if_fail(raw != NULL, NULL); 646 g_return_val_if_fail(raw != NULL, NULL);
650 647
651 /* Enclose text in one root tag, to try to make it valid XML for parsing. */ 648 /* Enclose text in one root tag, to try to make it valid XML for parsing. */
652 enclosed_raw = g_strconcat("<root>", raw, "</root>", NULL); 649 enclosed_raw = g_strconcat("<root>", raw, "</root>", NULL);
653 650
654 root = xmlnode_from_str(enclosed_raw, -1); 651 root = xmlnode_from_str(enclosed_raw, -1);
655 652
656 if (!root) { 653 if (!root) {
657 purple_debug_info("msim", "msim_markup_to_html: couldn't parse " 654 purple_debug_warning("msim", "msim_markup_to_html: couldn't parse "
658 "%s as XML, returning raw: %s\n", enclosed_raw, raw); 655 "%s as XML, returning raw: %s\n", enclosed_raw, raw);
659 /* TODO: msim_unrecognized */ 656 /* TODO: msim_unrecognized */
660 g_free(enclosed_raw); 657 g_free(enclosed_raw);
661 return g_strdup(raw); 658 return g_strdup(raw);
662 } 659 }
663 660
664 g_free(enclosed_raw); 661 g_free(enclosed_raw);
665 662
666 str = msim_convert_xmlnode(session, root, f, 0); 663 str = g_string_new(NULL);
667 g_return_val_if_fail(str != NULL, NULL); 664 msim_convert_xmlnode(session, str, root, f, 0);
668 purple_debug_info("msim", "msim_markup_to_html: returning %s\n", str);
669
670 xmlnode_free(root); 665 xmlnode_free(root);
671 666
672 return str; 667 purple_debug_info("msim", "msim_markup_to_html: returning %s\n", str->str);
668
669 return g_string_free(str, FALSE);
673 } 670 }
674 671
675 /** Convert plaintext smileys to <i> markup tags. 672 /** Convert plaintext smileys to <i> markup tags.
676 * 673 *
677 * @param before Original text with ASCII smileys. Will be freed. 674 * @param before Original text with ASCII smileys. Will be freed.
694 symbol = emote->symbol; 691 symbol = emote->symbol;
695 692
696 replacement = g_strdup_printf("<i n=\"%s\"/>", name); 693 replacement = g_strdup_printf("<i n=\"%s\"/>", name);
697 694
698 purple_debug_info("msim", "msim_convert_smileys_to_markup: %s->%s\n", 695 purple_debug_info("msim", "msim_convert_smileys_to_markup: %s->%s\n",
699 symbol ? symbol : "(NULL)", 696 symbol ? symbol : "(NULL)",
700 replacement ? replacement : "(NULL)"); 697 replacement ? replacement : "(NULL)");
701 new = purple_strreplace(old, symbol, replacement); 698 new = purple_strreplace(old, symbol, replacement);
702 699
703 g_free(replacement); 700 g_free(replacement);
704 g_free(old); 701 g_free(old);
705 702
706 old = new; 703 old = new;
707 } 704 }
708 705
709 return new; 706 return new;
710 } 707 }
711 708
712 709 /**
713 /** High-level function to convert MySpaceIM markup to Purple (HTML) markup. 710 * High-level function to convert MySpaceIM markup to Purple (HTML) markup.
714 * 711 *
715 * @return Purple markup string, must be g_free()'d. */ 712 * @return Purple markup string, must be g_free()'d. */
716 gchar * 713 gchar *
717 msim_markup_to_html(MsimSession *session, const gchar *raw) 714 msim_markup_to_html(MsimSession *session, const gchar *raw)
718 { 715 {
719 return msim_convert_xml(session, raw, 716 return msim_convert_xml(session, raw, msim_markup_tag_to_html);
720 (MSIM_XMLNODE_CONVERT)(msim_markup_tag_to_html)); 717 }
721 } 718
722 719 /**
723 /** High-level function to convert Purple (HTML) to MySpaceIM markup. 720 * High-level function to convert Purple (HTML) to MySpaceIM markup.
724 * 721 *
725 * TODO: consider using purple_markup_html_to_xhtml() to make valid XML. 722 * TODO: consider using purple_markup_html_to_xhtml() to make valid XML.
726 * 723 *
727 * @return HTML markup string, must be g_free()'d. */ 724 * @return HTML markup string, must be g_free()'d. */
728 gchar * 725 gchar *
729 html_to_msim_markup(MsimSession *session, const gchar *raw) 726 html_to_msim_markup(MsimSession *session, const gchar *raw)
730 { 727 {
731 gchar *markup; 728 gchar *markup;
732 729
733 markup = msim_convert_xml(session, raw, 730 markup = msim_convert_xml(session, raw, html_tag_to_msim_markup);
734 (MSIM_XMLNODE_CONVERT)(html_tag_to_msim_markup)); 731
735
736 if (purple_account_get_bool(session->account, "emoticons", TRUE)) { 732 if (purple_account_get_bool(session->account, "emoticons", TRUE)) {
737 /* Frees markup and allocates a new one. */ 733 /* Frees markup and allocates a new one. */
738 markup = msim_convert_smileys_to_markup(markup); 734 markup = msim_convert_smileys_to_markup(markup);
739 } 735 }
740 736
741 return markup; 737 return markup;
742 } 738 }
743
744