Mercurial > pidgin
view libpurple/protocols/myspace/markup.c @ 21098:7e200a629109
explicit merge of '8f24e3576448fefbc861e06f1e8d5fe17db2e71a'
and '49d0219884ede2c6c571f2df73e29dffa86f54ad'
author | Richard Laager <rlaager@wiktel.com> |
---|---|
date | Sun, 14 Oct 2007 02:13:14 +0000 |
parents | 2447efa8c93b |
children | 89c7be36fd61 36377c25417d |
line wrap: on
line source
/* MySpaceIM Protocol Plugin - markup * * Copyright (C) 2007, Jeff Connelly <jeff2@soc.pidgin.im> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include "myspace.h" typedef void (*MSIM_XMLNODE_CONVERT)(MsimSession *, xmlnode *, gchar **, gchar **); /* Internal functions */ static guint msim_point_to_purple_size(MsimSession *session, guint point); static guint msim_purple_size_to_point(MsimSession *session, guint size); static guint msim_height_to_point(MsimSession *session, guint height); static guint msim_point_to_height(MsimSession *session, guint point); static void msim_markup_tag_to_html(MsimSession *, xmlnode *root, gchar **begin, gchar **end); static void html_tag_to_msim_markup(MsimSession *, xmlnode *root, gchar **begin, gchar **end); static gchar *msim_convert_xml(MsimSession *, const gchar *raw, MSIM_XMLNODE_CONVERT f); static gchar *msim_convert_smileys_to_markup(gchar *before); static double msim_round(double round); /* Globals */ /* The names in in emoticon_names (for <i n=whatever>) map to corresponding * entries in emoticon_symbols (for the ASCII representation of the emoticon). * * Multiple emoticon symbols in Pidgin can map to one name. List the * canonical form, as inserted by the "Smile!" dialog, first. For example, * :) comes before :-), because although both are recognized as 'happy', * the first is inserted by the smiley button (first symbol in theme). * * Note that symbols are case-sensitive in Pidgin -- :-X is not :-x. */ static struct MSIM_EMOTICON { gchar *name; gchar *symbol; } msim_emoticons[] = { /* Unfortunately, this list duplicates much of the file * pidgin/pidgin/pixmaps/emotes/default/22/default.theme.in, because * that file is part of Pidgin, but we're part of libpurple. */ { "bigsmile", ":D" }, { "bigsmile", ":-D" }, { "devil", "}:)" }, { "frazzled", ":Z" }, { "geek", "B)" }, { "googles", "%)" }, { "growl", ":E" }, { "laugh", ":))" }, /* Must be before ':)' */ { "happy", ":)" }, { "happy", ":-)" }, { "happi", ":)" }, { "heart", ":X" }, { "mohawk", "-:" }, { "mad", "X(" }, { "messed", "X)" }, { "nerd", "Q)" }, { "oops", ":G" }, { "pirate", "P)" }, { "scared", ":O" }, { "sidefrown", ":{" }, { "sinister", ":B" }, { "smirk", ":," }, { "straight", ":|" }, { "tongue", ":P" }, { "tongue", ":p" }, { "tongy", ":P" }, { "upset", "B|" }, { "wink", ";-)" }, { "wink", ";)" }, { "winc", ";)" }, { "worried", ":[" }, { "kiss", ":x" }, { NULL, NULL } }; /* Indexes of this array + 1 map HTML font size to scale of normal font size. * * Based on _point_sizes from libpurple/gtkimhtml.c * 1 2 3 4 5 6 7 */ static gdouble _font_scale[] = { .85, .95, 1, 1.2, 1.44, 1.728, 2.0736 }; #define MAX_FONT_SIZE 7 /* Purple maximum font size */ #define POINTS_PER_INCH 72 /* How many pt's in an inch */ /* Text formatting bits for <f s=#> */ #define MSIM_TEXT_BOLD 1 #define MSIM_TEXT_ITALIC 2 #define MSIM_TEXT_UNDERLINE 4 /* Default baseline size of purple's fonts, in points. What is size 3 in points. * _font_scale specifies scaling factor relative to this point size. Note this * is only the default; it is configurable in account options. */ #define MSIM_BASE_FONT_POINT_SIZE 8 /* Default display's DPI. 96 is common but it can differ. Also configurable * in account options. */ #define MSIM_DEFAULT_DPI 96 /* round is part of C99, but sometimes is unavailable before then. * Based on http://forums.belution.com/en/cpp/000/050/13.shtml */ double msim_round(double value) { if (value < 0) { return -(floor(-value + 0.5)); } else { return floor( value + 0.5); } } /** Convert typographical font point size to HTML font size. * Based on libpurple/gtkimhtml.c */ static guint msim_point_to_purple_size(MsimSession *session, guint point) { guint size, this_point, base; gdouble scale; base = purple_account_get_int(session->account, "base_font_size", MSIM_BASE_FONT_POINT_SIZE); for (size = 0; size < sizeof(_font_scale) / sizeof(_font_scale[0]); ++size) { scale = _font_scale[CLAMP(size, 1, MAX_FONT_SIZE) - 1]; this_point = (guint)msim_round(scale * base); if (this_point >= point) { purple_debug_info("msim", "msim_point_to_purple_size: %d pt -> size=%d\n", point, size); return size; } } /* No HTML font size was this big; return largest possible. */ return this_point; } /** Convert HTML font size to point size. */ static guint msim_purple_size_to_point(MsimSession *session, guint size) { gdouble scale; guint point; guint base; scale = _font_scale[CLAMP(size, 1, MAX_FONT_SIZE) - 1]; base = purple_account_get_int(session->account, "base_font_size", MSIM_BASE_FONT_POINT_SIZE); point = (guint)msim_round(scale * base); purple_debug_info("msim", "msim_purple_size_to_point: size=%d -> %d pt\n", size, point); return point; } /** Convert a msim markup font pixel height to the more usual point size, for incoming messages. */ static guint msim_height_to_point(MsimSession *session, guint height) { guint dpi; dpi = purple_account_get_int(session->account, "port", MSIM_DEFAULT_DPI); return (guint)msim_round((POINTS_PER_INCH * 1. / dpi) * height); /* See also: libpurple/protocols/bonjour/jabber.c * _font_size_ichat_to_purple */ } /** Convert point size to msim pixel height font size specification, for outgoing messages. */ static guint msim_point_to_height(MsimSession *session, guint point) { guint dpi; dpi = purple_account_get_int(session->account, "port", MSIM_DEFAULT_DPI); return (guint)msim_round((dpi * 1. / POINTS_PER_INCH) * point); } /** Convert the msim markup <f> (font) tag into HTML. */ static void msim_markup_f_to_html(MsimSession *session, xmlnode *root, gchar **begin, gchar **end) { const gchar *face, *height_str, *decor_str; GString *gs_end, *gs_begin; guint decor, height; face = xmlnode_get_attrib(root, "f"); height_str = xmlnode_get_attrib(root, "h"); decor_str = xmlnode_get_attrib(root, "s"); if (height_str) { height = atol(height_str); } else { height = 12; } if (decor_str) { decor = atol(decor_str); } else { decor = 0; } gs_begin = g_string_new(""); /* TODO: get font size working */ if (height && !face) { g_string_printf(gs_begin, "<font size='%d'>", msim_point_to_purple_size(session, msim_height_to_point(session, height))); } else if (height && face) { g_string_printf(gs_begin, "<font face='%s' size='%d'>", face, msim_point_to_purple_size(session, msim_height_to_point(session, height))); } else { g_string_printf(gs_begin, "<font>"); } /* No support for font-size CSS? */ /* g_string_printf(gs_begin, "<span style='font-family: %s; font-size: %dpt'>", face, msim_height_to_point(height)); */ gs_end = g_string_new("</font>"); if (decor & MSIM_TEXT_BOLD) { g_string_append(gs_begin, "<b>"); g_string_prepend(gs_end, "</b>"); } if (decor & MSIM_TEXT_ITALIC) { g_string_append(gs_begin, "<i>"); g_string_append(gs_end, "</i>"); } if (decor & MSIM_TEXT_UNDERLINE) { g_string_append(gs_begin, "<u>"); g_string_append(gs_end, "</u>"); } *begin = g_string_free(gs_begin, FALSE); *end = g_string_free(gs_end, FALSE); } /** Convert a msim markup color to a color suitable for libpurple. * * @param msim Either a color name, or an rgb(x,y,z) code. * * @return A new string, either a color name or #rrggbb code. Must g_free(). */ static char * msim_color_to_purple(const char *msim) { guint red, green, blue; if (!msim) { return g_strdup("black"); } if (sscanf(msim, "rgb(%d,%d,%d)", &red, &green, &blue) != 3) { /* Color name. */ return g_strdup(msim); } /* TODO: rgba (alpha). */ return g_strdup_printf("#%.2x%.2x%.2x", red, green, blue); } /** Convert the msim markup <a> (anchor) tag into HTML. */ static void msim_markup_a_to_html(MsimSession *session, xmlnode *root, gchar **begin, gchar **end) { const gchar *href; href = xmlnode_get_attrib(root, "h"); if (!href) { href = ""; } *begin = g_strdup_printf("<a href=\"%s\">%s", href, href); *end = g_strdup("</a>"); } /** Convert the msim markup <p> (paragraph) tag into HTML. */ static void msim_markup_p_to_html(MsimSession *session, xmlnode *root, gchar **begin, gchar **end) { /* Just pass through unchanged. * * Note: attributes currently aren't passed, if there are any. */ *begin = g_strdup("<p>"); *end = g_strdup("</p>"); } /** Convert the msim markup <c> tag (text color) into HTML. TODO: Test */ static void msim_markup_c_to_html(MsimSession *session, xmlnode *root, gchar **begin, gchar **end) { const gchar *color; gchar *purple_color; color = xmlnode_get_attrib(root, "v"); if (!color) { purple_debug_info("msim", "msim_markup_c_to_html: <c> tag w/o v attr\n"); *begin = g_strdup(""); *end = g_strdup(""); /* TODO: log as unrecognized */ return; } purple_color = msim_color_to_purple(color); *begin = g_strdup_printf("<font color='%s'>", purple_color); g_free(purple_color); /* *begin = g_strdup_printf("<span style='color: %s'>", color); */ *end = g_strdup("</font>"); } /** Convert the msim markup <b> tag (background color) into HTML. TODO: Test */ static void msim_markup_b_to_html(MsimSession *session, xmlnode *root, gchar **begin, gchar **end) { const gchar *color; gchar *purple_color; color = xmlnode_get_attrib(root, "v"); if (!color) { *begin = g_strdup(""); *end = g_strdup(""); purple_debug_info("msim", "msim_markup_b_to_html: <b> w/o v attr\n"); /* TODO: log as unrecognized. */ return; } purple_color = msim_color_to_purple(color); /* TODO: find out how to set background color. */ *begin = g_strdup_printf("<span style='background-color: %s'>", purple_color); g_free(purple_color); *end = g_strdup("</p>"); } /** Convert the msim markup <i> tag (emoticon image) into HTML. */ static void msim_markup_i_to_html(MsimSession *session, xmlnode *root, gchar **begin, gchar **end) { const gchar *name; guint i; struct MSIM_EMOTICON *emote; name = xmlnode_get_attrib(root, "n"); if (!name) { purple_debug_info("msim", "msim_markup_i_to_html: <i> w/o n\n"); *begin = g_strdup(""); *end = g_strdup(""); /* TODO: log as unrecognized */ return; } /* Find and use canonical form of smiley symbol. */ for (i = 0; (emote = &msim_emoticons[i]) && emote->name != NULL; ++i) { if (g_str_equal(name, emote->name)) { *begin = g_strdup(emote->symbol); *end = g_strdup(""); return; } } /* Couldn't find it, sorry. Try to degrade gracefully. */ *begin = g_strdup_printf("**%s**", name); *end = g_strdup(""); } /** Convert an individual msim markup tag to HTML. */ static void msim_markup_tag_to_html(MsimSession *session, xmlnode *root, gchar **begin, gchar **end) { g_return_if_fail(root != NULL); if (g_str_equal(root->name, "f")) { msim_markup_f_to_html(session, root, begin, end); } else if (g_str_equal(root->name, "a")) { msim_markup_a_to_html(session, root, begin, end); } else if (g_str_equal(root->name, "p")) { msim_markup_p_to_html(session, root, begin, end); } else if (g_str_equal(root->name, "c")) { msim_markup_c_to_html(session, root, begin, end); } else if (g_str_equal(root->name, "b")) { msim_markup_b_to_html(session, root, begin, end); } else if (g_str_equal(root->name, "i")) { msim_markup_i_to_html(session, root, begin, end); } else { purple_debug_info("msim", "msim_markup_tag_to_html: " "unknown tag name=%s, ignoring", root->name ? root->name : "(NULL)"); *begin = g_strdup(""); *end = g_strdup(""); } } /** Convert an individual HTML tag to msim markup. */ static void html_tag_to_msim_markup(MsimSession *session, xmlnode *root, gchar **begin, gchar **end) { if (!purple_utf8_strcasecmp(root->name, "root") || !purple_utf8_strcasecmp(root->name, "html")) { *begin = g_strdup(""); *end = g_strdup(""); /* TODO: Coalesce nested tags into one <f> tag! * Currently, the 's' value will be overwritten when b/i/u is nested * within another one, and only the inner-most formatting will be * applied to the text. */ } else if (!purple_utf8_strcasecmp(root->name, "b")) { *begin = g_strdup_printf("<f s='%d'>", MSIM_TEXT_BOLD); *end = g_strdup("</f>"); } else if (!purple_utf8_strcasecmp(root->name, "i")) { *begin = g_strdup_printf("<f s='%d'>", MSIM_TEXT_ITALIC); *end = g_strdup("</f>"); } else if (!purple_utf8_strcasecmp(root->name, "u")) { *begin = g_strdup_printf("<f s='%d'>", MSIM_TEXT_UNDERLINE); *end = g_strdup("</f>"); } else if (!purple_utf8_strcasecmp(root->name, "a")) { const gchar *href; gchar *link_text; href = xmlnode_get_attrib(root, "href"); if (!href) { href = xmlnode_get_attrib(root, "HREF"); } link_text = xmlnode_get_data(root); if (href) { if (g_str_equal(link_text, href)) { /* Purple gives us: <a href="URL">URL</a> * Translate to <a h='URL' /> * Displayed as text of URL with link to URL */ *begin = g_strdup_printf("<a h='%s' />", href); } else { /* But if we get: <a href="URL">text</a> * Translate to: text: <a h='URL' /> * * Because official client only supports self-closed <a> * tags; you can't change the link text. */ *begin = g_strdup_printf("%s: <a h='%s' />", link_text, href); } } else { *begin = g_strdup("<a />"); } /* Sorry, kid. MySpace doesn't support you within <a> tags. */ xmlnode_free(root->child); g_free(link_text); root->child = NULL; *end = g_strdup(""); } else if (!purple_utf8_strcasecmp(root->name, "font")) { const gchar *size; const gchar *face; size = xmlnode_get_attrib(root, "size"); face = xmlnode_get_attrib(root, "face"); if (face && size) { *begin = g_strdup_printf("<f f='%s' h='%d'>", face, msim_point_to_height(session, msim_purple_size_to_point(session, atoi(size)))); } else if (face) { *begin = g_strdup_printf("<f f='%s'>", face); } else if (size) { *begin = g_strdup_printf("<f h='%d'>", msim_point_to_height(session, msim_purple_size_to_point(session, atoi(size)))); } else { *begin = g_strdup("<f>"); } *end = g_strdup("</f>"); /* TODO: color (bg uses <body>), emoticons */ } else { gchar *err; #ifdef MSIM_MARKUP_SHOW_UNKNOWN_TAGS *begin = g_strdup_printf("[%s]", root->name); *end = g_strdup_printf("[/%s]", root->name); #else *begin = g_strdup(""); *end = g_strdup(""); #endif err = g_strdup_printf("html_tag_to_msim_markup: unrecognized " "HTML tag %s was sent by the IM client; ignoring", root->name ? root->name : "(NULL)"); msim_unrecognized(NULL, NULL, err); g_free(err); } } /** Convert an xmlnode of msim markup or HTML to an HTML string or msim markup. * * @param f Function to convert tags. * * @return An HTML string. Caller frees. */ static gchar * msim_convert_xmlnode(MsimSession *session, xmlnode *root, MSIM_XMLNODE_CONVERT f) { xmlnode *node; gchar *begin, *inner, *end; GString *final; if (!root || !root->name) { return g_strdup(""); } purple_debug_info("msim", "msim_convert_xmlnode: got root=%s\n", root->name); begin = inner = end = NULL; final = g_string_new(""); f(session, root, &begin, &end); g_string_append(final, begin); /* Loop over all child nodes. */ for (node = root->child; node != NULL; node = node->next) { switch (node->type) { case XMLNODE_TYPE_ATTRIB: /* Attributes handled above. */ break; case XMLNODE_TYPE_TAG: /* A tag or tag with attributes. Recursively descend. */ inner = msim_convert_xmlnode(session, node, f); g_return_val_if_fail(inner != NULL, NULL); purple_debug_info("msim", " ** node name=%s\n", (node && node->name) ? node->name : "(NULL)"); break; case XMLNODE_TYPE_DATA: /* Literal text. */ inner = g_strndup(node->data, node->data_sz); purple_debug_info("msim", " ** node data=%s\n", inner ? inner : "(NULL)"); break; default: purple_debug_info("msim", "msim_convert_xmlnode: strange node\n"); inner = g_strdup(""); } if (inner) { g_string_append(final, inner); g_free(inner); inner = NULL; } } /* TODO: Note that msim counts each piece of text enclosed by <f> as * a paragraph and will display each on its own line. You actually have * to _nest_ <f> tags to intersperse different text in one paragraph! * Comment out this line below to see. */ g_string_append(final, end); g_free(begin); g_free(end); purple_debug_info("msim", "msim_markup_xmlnode_to_gtkhtml: RETURNING %s\n", (final && final->str) ? final->str : "(NULL)"); return g_string_free(final, FALSE); } /** Convert XML to something based on MSIM_XMLNODE_CONVERT. */ static gchar * msim_convert_xml(MsimSession *session, const gchar *raw, MSIM_XMLNODE_CONVERT f) { xmlnode *root; gchar *str; gchar *enclosed_raw; g_return_val_if_fail(raw != NULL, NULL); /* Enclose text in one root tag, to try to make it valid XML for parsing. */ enclosed_raw = g_strconcat("<root>", raw, "</root>", NULL); root = xmlnode_from_str(enclosed_raw, -1); if (!root) { purple_debug_info("msim", "msim_markup_to_html: couldn't parse " "%s as XML, returning raw: %s\n", enclosed_raw, raw); /* TODO: msim_unrecognized */ g_free(enclosed_raw); return g_strdup(raw); } g_free(enclosed_raw); str = msim_convert_xmlnode(session, root, f); g_return_val_if_fail(str != NULL, NULL); purple_debug_info("msim", "msim_markup_to_html: returning %s\n", str); xmlnode_free(root); return str; } /** Convert plaintext smileys to <i> markup tags. * * @param before Original text with ASCII smileys. Will be freed. * @return A new string with <i> tags, if applicable. Must be g_free()'d. */ static gchar * msim_convert_smileys_to_markup(gchar *before) { gchar *old, *new, *replacement; guint i; struct MSIM_EMOTICON *emote; old = before; new = NULL; for (i = 0; (emote = &msim_emoticons[i]) && emote->name != NULL; ++i) { gchar *name, *symbol; name = emote->name; symbol = emote->symbol; replacement = g_strdup_printf("<i n=\"%s\"/>", name); purple_debug_info("msim", "msim_convert_smileys_to_markup: %s->%s\n", symbol ? symbol : "(NULL)", replacement ? replacement : "(NULL)"); new = purple_strreplace(old, symbol, replacement); g_free(replacement); g_free(old); old = new; } return new; } /** High-level function to convert MySpaceIM markup to Purple (HTML) markup. * * @return Purple markup string, must be g_free()'d. */ gchar * msim_markup_to_html(MsimSession *session, const gchar *raw) { return msim_convert_xml(session, raw, (MSIM_XMLNODE_CONVERT)(msim_markup_tag_to_html)); } /** High-level function to convert Purple (HTML) to MySpaceIM markup. * * TODO: consider using purple_markup_html_to_xhtml() to make valid XML. * * @return HTML markup string, must be g_free()'d. */ gchar * html_to_msim_markup(MsimSession *session, const gchar *raw) { gchar *markup; markup = msim_convert_xml(session, raw, (MSIM_XMLNODE_CONVERT)(html_tag_to_msim_markup)); if (purple_account_get_bool(session->account, "emoticons", TRUE)) { /* Frees markup and allocates a new one. */ markup = msim_convert_smileys_to_markup(markup); } return markup; }