diff 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
line wrap: on
line diff
--- a/libpurple/protocols/myspace/markup.c	Sat Nov 29 18:46:49 2008 +0000
+++ b/libpurple/protocols/myspace/markup.c	Sun Dec 21 18:32:37 2008 +0000
@@ -21,23 +21,9 @@
 
 typedef int (*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 int msim_markup_tag_to_html(MsimSession *, xmlnode *root, gchar **begin, gchar **end);
-static int 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 
+/* 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
@@ -90,23 +76,23 @@
 	{ 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 
+ * 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 */
+/* Purple maximum font size.  Equivalent to sizeof(_font_scale) / sizeof(_font_scale[0]) */
+#define MAX_FONT_SIZE                   7
+
 #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_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 
+/* 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
 
@@ -114,11 +100,10 @@
  * 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)
+static double msim_round(double value)
 {
 	if (value < 0) {
 		return -(floor(-value + 0.5));
@@ -127,22 +112,17 @@
 	}
 }
 
-
-/** Convert typographical font point size to HTML font size. 
+/** 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);
+
+	for (size = 0; size < MAX_FONT_SIZE; ++size) {
+		this_point = (guint)msim_round(base * _font_scale[size]);
 
 		if (this_point >= point) {
 			purple_debug_info("msim", "msim_point_to_purple_size: %d pt -> size=%d\n",
@@ -176,7 +156,7 @@
 }
 
 /** Convert a msim markup font pixel height to the more usual point size, for incoming messages. */
-static guint 
+static guint
 msim_height_to_point(MsimSession *session, guint height)
 {
 	guint dpi;
@@ -201,7 +181,7 @@
 }
 
 /** Convert the msim markup <f> (font) tag into HTML. */
-static void 
+static void
 msim_markup_f_to_html(MsimSession *session, xmlnode *root, gchar **begin, gchar **end)
 {
 	const gchar *face, *height_str, *decor_str;
@@ -212,35 +192,38 @@
 	height_str = xmlnode_get_attrib(root, "h");
 	decor_str = xmlnode_get_attrib(root, "s");
 
-	if (height_str) {
-		height = atol(height_str);
+	/* Validate the font face, to avoid constructing invalid HTML later */
+	if (face != NULL && strchr(face, '\'') != NULL)
+		face = NULL;
+
+	height = height_str != NULL ? atol(height_str) : 12;
+	decor = decor_str != NULL ? atol(decor_str) : 0;
+
+	/*
+	 * The HTML we're constructing here is a bit redudant.  Ideally we
+	 * would use only the font-family and font-size CSS span, but Pidgin
+	 * doesn't support it (it's included for other UIs).  For Pidgin we
+	 * wrap the whole thing in an ugly font tag, and Pidgin will happily
+	 * ignore the <span>.
+	 */
+	gs_begin = g_string_new("");
+	if (height && !face) {
+		guint point_size = msim_height_to_point(session, height);
+		g_string_printf(gs_begin,
+				"<font size='%d'><span style='font-size: %dpt'>",
+				msim_point_to_purple_size(session, point_size),
+				point_size);
+	} else if (height && face) {
+		guint point_size = msim_height_to_point(session, height);
+		g_string_printf(gs_begin,
+				"<font face='%s' size='%d'><span style='font-family: %s; font-size: %dpt'>",
+				face, msim_point_to_purple_size(session, point_size),
+				face, point_size);
 	} else {
-		height = 12;
-	}
-
-	if (decor_str) {
-		decor = atol(decor_str);
-	} else {
-		decor = 0;
+		g_string_printf(gs_begin, "<font><span>");
 	}
 
-	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>");
+	gs_end = g_string_new("</span></font>");
 
 	if (decor & MSIM_TEXT_BOLD) {
 		g_string_append(gs_begin, "<b>");
@@ -257,17 +240,16 @@
 		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(). 
-  */
+ *
+ * @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)
 {
@@ -287,7 +269,7 @@
 }
 
 /** Convert the msim markup <a> (anchor) tag into HTML. */
-static void 
+static void
 msim_markup_a_to_html(MsimSession *session, xmlnode *root, gchar **begin, gchar **end)
 {
 	const gchar *href;
@@ -302,18 +284,20 @@
 }
 
 /** Convert the msim markup <p> (paragraph) tag into HTML. */
-static void 
+static void
 msim_markup_p_to_html(MsimSession *session, xmlnode *root, gchar **begin, gchar **end)
 {
-	/* Just pass through unchanged. 
+	/* 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 
+/**
+ * Convert the msim markup <c> tag (text color) into HTML.
+ */
+static void
 msim_markup_c_to_html(MsimSession *session, xmlnode *root, gchar **begin, gchar **end)
 {
 	const gchar *color;
@@ -330,16 +314,21 @@
 
 	purple_color = msim_color_to_purple(color);
 
-	*begin = g_strdup_printf("<font color='%s'>", purple_color); 
+#ifdef USE_CSS_FORMATTING
+	*begin = g_strdup_printf("<span style='color: %s'>", purple_color);
+	*end = g_strdup("</span>");
+#else
+	*begin = g_strdup_printf("<font color='%s'>", purple_color);
+	*end = g_strdup("</font>");
+#endif
 
 	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 
+/**
+ * Convert the msim markup <b> tag (background color) into HTML.
+ */
+static void
 msim_markup_b_to_html(MsimSession *session, xmlnode *root, gchar **begin, gchar **end)
 {
 	const gchar *color;
@@ -356,16 +345,19 @@
 
 	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);
+#ifdef USE_CSS_FORMATTING
+	*begin = g_strdup_printf("<span style='background-color: %s'>", purple_color);
+	*end = g_strdup("</span>");
+#else
+	*begin = g_strdup_printf("<body bgcolor='%s'>", purple_color);
+	*end = g_strdup("</body>");
+#endif
+
 	g_free(purple_color);
-
-	*end = g_strdup("</p>");
 }
 
 /** Convert the msim markup <i> tag (emoticon image) into HTML. */
-static void 
+static void
 msim_markup_i_to_html(MsimSession *session, xmlnode *root, gchar **begin, gchar **end)
 {
 	const gchar *name;
@@ -396,8 +388,8 @@
 }
 
 /** Convert an individual msim markup tag to HTML. */
-static int 
-msim_markup_tag_to_html(MsimSession *session, xmlnode *root, gchar **begin, 
+static int
+msim_markup_tag_to_html(MsimSession *session, xmlnode *root, gchar **begin,
 		gchar **end)
 {
 	g_return_val_if_fail(root != NULL, 0);
@@ -416,7 +408,7 @@
 		msim_markup_i_to_html(session, root, begin, end);
 	} else {
 		purple_debug_info("msim", "msim_markup_tag_to_html: "
-				"unknown tag name=%s, ignoring", 
+				"unknown tag name=%s, ignoring\n",
 				root->name ? root->name : "(NULL)");
 		*begin = g_strdup("");
 		*end = g_strdup("");
@@ -425,8 +417,8 @@
 }
 
 /** Convert an individual HTML tag to msim markup. */
-static int 
-html_tag_to_msim_markup(MsimSession *session, xmlnode *root, gchar **begin, 
+static int
+html_tag_to_msim_markup(MsimSession *session, xmlnode *root, gchar **begin,
 		gchar **end)
 {
 	int ret = 0;
@@ -437,7 +429,7 @@
 		*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 
+	 * within another one, and only the inner-most formatting will be
 	 * applied to the text. */
 	} else if (!purple_utf8_strcasecmp(root->name, "b")) {
 		if (root->child->type == XMLNODE_TYPE_DATA) {
@@ -515,29 +507,47 @@
 
 		*end = g_strdup("");
 	} else if (!purple_utf8_strcasecmp(root->name, "font")) {
+		GString *tmpbegin, *tmpend;
 		const gchar *size;
 		const gchar *face;
+		const gchar *color;
 
 		size = xmlnode_get_attrib(root, "size");
 		face = xmlnode_get_attrib(root, "face");
+		color = xmlnode_get_attrib(root, "color");
 
-		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'>", 
+		tmpbegin = g_string_new("<f");
+		tmpend = g_string_new("</f>");
+
+		if (face != NULL)
+			g_string_append_printf(tmpbegin, "f='%s'", face);
+
+		if (size != NULL)
+			g_string_append_printf(tmpbegin, "h='%d'",
 					 msim_point_to_height(session,
 						 msim_purple_size_to_point(session, atoi(size))));
-		} else {
-			*begin = g_strdup("<f>");
+
+		/* Close the <f> tag */
+		g_string_append(tmpbegin, ">");
+
+		if (color != NULL) {
+			g_string_append_printf(tmpbegin, "<c v='%s'>", color);
+			g_string_prepend(tmpend, "</c>");
 		}
 
-		*end = g_strdup("</f>");
+		*begin = g_string_free(tmpbegin, FALSE);
+		*end = g_string_free(tmpend, FALSE);
+
+	} else if (!purple_utf8_strcasecmp(root->name, "body")) {
+		const gchar *bgcolor;
 
-		/* TODO: color (bg uses <body>), emoticons */
+		bgcolor = xmlnode_get_attrib(root, "bgcolor");
+
+		if (bgcolor != NULL) {
+			*begin = g_strdup_printf("<b v='%s'>", bgcolor);
+			*end = g_strdup("</b>");
+		}
+
 	} else {
 		gchar *err;
 
@@ -550,7 +560,7 @@
 #endif
 
 		err = g_strdup_printf("html_tag_to_msim_markup: unrecognized "
-			"HTML tag %s was sent by the IM client; ignoring", 
+			"HTML tag %s was sent by the IM client; ignoring",
 			root->name ? root->name : "(NULL)");
 		msim_unrecognized(NULL, NULL, err);
 		g_free(err);
@@ -564,29 +574,26 @@
  *
  * @return An HTML string. Caller frees.
  */
-static gchar *
-msim_convert_xmlnode(MsimSession *session, xmlnode *root, MSIM_XMLNODE_CONVERT f, int nodes_processed)
+static void
+msim_convert_xmlnode(MsimSession *session, GString *out, xmlnode *root, MSIM_XMLNODE_CONVERT f, int nodes_processed)
 {
 	xmlnode *node;
-	gchar *begin, *inner, *end;
-	GString *final;
+	gchar *begin, *inner, *end, *tmp;
 	int descended = nodes_processed;
 
-	if (!root || !root->name) {
-		return g_strdup("");
-	}
+	if (!root || !root->name)
+		return;
 
 	purple_debug_info("msim", "msim_convert_xmlnode: got root=%s\n",
 			root->name);
 
 	begin = inner = end = NULL;
 
-	final = g_string_new("");
-
 	if (descended == 0) /* We've not formatted this yet.. :) */
 		descended = f(session, root, &begin, &end); /* Get the value that our format function has already descended for us */
-	
-	g_string_append(final, begin);
+
+	g_string_append(out, begin);
+	g_free(begin);
 
 	/* Loop over all child nodes. */
 	for (node = root->child; node != NULL; node = node->next) {
@@ -597,29 +604,26 @@
 
 			case XMLNODE_TYPE_TAG:
 				/* A tag or tag with attributes. Recursively descend. */
-				inner = msim_convert_xmlnode(session, node, f, descended);
-				g_return_val_if_fail(inner != NULL, NULL);
-		
-				purple_debug_info("msim", " ** node name=%s\n", 
-						(node && node->name) ? node->name : "(NULL)");
+				msim_convert_xmlnode(session, out, node, f, descended);
+
+				purple_debug_info("msim", " ** node name=%s\n",
+						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)");
+				/*
+				 * TODO: Why is it necessary to escape here?  I thought
+				 *       node->data was already escaped?
+				 */
+				tmp = g_markup_escape_text(node->data, node->data_sz);
+				g_string_append(out, tmp);
+				g_free(tmp);
 				break;
-		
+
 			default:
-				purple_debug_info("msim",
-						"msim_convert_xmlnode: strange node\n");
-		}
-
-		if (inner) {
-			g_string_append(final, inner);
-			g_free(inner);
-			inner = NULL;
+				purple_debug_warning("msim",
+						"msim_convert_xmlnode: unknown node type\n");
 		}
 	}
 
@@ -627,15 +631,8 @@
 	 * 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_string_append(out, end);
 	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. */
@@ -643,7 +640,7 @@
 msim_convert_xml(MsimSession *session, const gchar *raw, MSIM_XMLNODE_CONVERT f)
 {
 	xmlnode *root;
-	gchar *str;
+	GString *str;
 	gchar *enclosed_raw;
 
 	g_return_val_if_fail(raw != NULL, NULL);
@@ -654,7 +651,7 @@
 	root = xmlnode_from_str(enclosed_raw, -1);
 
 	if (!root) {
-		purple_debug_info("msim", "msim_markup_to_html: couldn't parse "
+		purple_debug_warning("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);
@@ -663,13 +660,13 @@
 
 	g_free(enclosed_raw);
 
-	str = msim_convert_xmlnode(session, root, f, 0);
-	g_return_val_if_fail(str != NULL, NULL);
-	purple_debug_info("msim", "msim_markup_to_html: returning %s\n", str);
-
+	str = g_string_new(NULL);
+	msim_convert_xmlnode(session, str, root, f, 0);
 	xmlnode_free(root);
 
-	return str;
+	purple_debug_info("msim", "msim_markup_to_html: returning %s\n", str->str);
+
+	return g_string_free(str, FALSE);
 }
 
 /** Convert plaintext smileys to <i> markup tags.
@@ -696,10 +693,10 @@
 		replacement = g_strdup_printf("<i n=\"%s\"/>", name);
 
 		purple_debug_info("msim", "msim_convert_smileys_to_markup: %s->%s\n",
-				symbol ? symbol : "(NULL)", 
+				symbol ? symbol : "(NULL)",
 				replacement ? replacement : "(NULL)");
 		new = purple_strreplace(old, symbol, replacement);
-		
+
 		g_free(replacement);
 		g_free(old);
 
@@ -708,19 +705,19 @@
 
 	return new;
 }
-	
 
-/** High-level function to convert MySpaceIM markup to Purple (HTML) markup. 
+/**
+ * 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));
+	return msim_convert_xml(session, raw, msim_markup_tag_to_html);
 }
 
-/** High-level function to convert Purple (HTML) to MySpaceIM markup.
+/**
+ * High-level function to convert Purple (HTML) to MySpaceIM markup.
  *
  * TODO: consider using purple_markup_html_to_xhtml() to make valid XML.
  *
@@ -730,9 +727,8 @@
 {
 	gchar *markup;
 
-	markup = msim_convert_xml(session, raw,
-			(MSIM_XMLNODE_CONVERT)(html_tag_to_msim_markup));
-	
+	markup = msim_convert_xml(session, raw, 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);
@@ -740,5 +736,3 @@
 
 	return markup;
 }
-
-