From: Emden R. Gansner Date: Sun, 7 Jun 2015 19:12:48 +0000 (-0400) Subject: Fix tooltips to support line breaks and escString substitutions X-Git-Tag: TRAVIS_CI_BUILD_EXPERIMENTAL~109^2~14 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=0d40fee4f585e70dbc1bea73940e0fe853596e7a;p=graphviz Fix tooltips to support line breaks and escString substitutions as in the documentation; map ordinary tooltips to UTF8, converting HTML escape sequences. This makes the handling of tooltips uniform whether coming from a tooltip attribute or from an HTML-like label. --- diff --git a/lib/common/emit.c b/lib/common/emit.c index cc2744aa0..b8af0a639 100644 --- a/lib/common/emit.c +++ b/lib/common/emit.c @@ -244,6 +244,76 @@ getObjId (GVJ_t* job, void* obj, agxbuf* xb) return agxbuse(xb); } +/* interpretCRNL: + * Map "\n" to ^J, "\r" to ^M and "\l" to ^J. + * Map "\\" to backslash. + * Map "\x" to x. + * Mapping is done in place. + * Return input string. + */ + +static char* +interpretCRNL (char* ins) +{ + char* rets = ins; + char* outs = ins; + char c; + boolean backslash_seen = FALSE; + + while ((c = *ins++)) { + if (backslash_seen) { + switch (c) { + case 'n' : + case 'l' : + *outs++ = '\n'; + break; + case 'r' : + *outs++ = '\r'; + break; + default : + *outs++ = c; + break; + } + backslash_seen = FALSE; + } + else { + if (c == '\\') + backslash_seen = TRUE; + else + *outs++ = c; + } + } + *outs = '\0'; + return rets; +} + +/* preprocessTooltip: + * Tooltips are a weak form of escString, so we expect object substitution + * and newlines to be handled. The former occurs in initMapData. Here we + * map "\r", "\l" and "\n" to newlines. (We don't try to handle alignment + * as in real labels.) To make things uniform when the + * tooltip is emitted latter as visible text, we also convert HTML escape + * sequences into UTF8. This is already occurring when tooltips are input + * via HTML-like tables. + */ +static char* +preprocessTooltip(char* s, void* gobj) +{ + Agraph_t* g = agroot(gobj); + int charset = GD_charset(g); + char* news; + switch (charset) { + case CHAR_LATIN1: + news = latin1ToUTF8(s); + break; + default: /* UTF8 */ + news = htmlEntityUTF8(s, g); + break; + } + + return interpretCRNL (news); +} + static void initObjMapData (GVJ_t* job, textlabel_t *lab, void* gobj) { @@ -262,8 +332,11 @@ initObjMapData (GVJ_t* job, textlabel_t *lab, void* gobj) if (!url || !*url) /* try URL as an alias for href */ url = agget(gobj, "URL"); id = getObjId (job, gobj, &xb); + if (tooltip) + tooltip = preprocessTooltip (tooltip, gobj); initMapData (job, lbl, url, tooltip, target, id, gobj); + free (tooltip); agxbfree(&xb); } @@ -2564,28 +2637,36 @@ static void emit_begin_edge(GVJ_t * job, edge_t * e, char** styles) if (flags & GVRENDER_DOES_TOOLTIPS) { if (((s = agget(e, "tooltip")) && s[0]) || ((s = agget(e, "edgetooltip")) && s[0])) { - obj->tooltip = strdup_and_subst_obj(s, (void*)e); + char* tooltip = preprocessTooltip (s, e); + obj->tooltip = strdup_and_subst_obj(tooltip, (void*)e); + free (tooltip); obj->explicit_tooltip = TRUE; } else if (obj->label) obj->tooltip = strdup(obj->label); if ((s = agget(e, "labeltooltip")) && s[0]) { - obj->labeltooltip = strdup_and_subst_obj(s, (void*)e); + char* tooltip = preprocessTooltip (s, e); + obj->labeltooltip = strdup_and_subst_obj(tooltip, (void*)e); + free (tooltip); obj->explicit_labeltooltip = TRUE; } else if (obj->label) obj->labeltooltip = strdup(obj->label); if ((s = agget(e, "tailtooltip")) && s[0]) { - obj->tailtooltip = strdup_and_subst_obj(s, (void*)e); + char* tooltip = preprocessTooltip (s, e); + obj->tailtooltip = strdup_and_subst_obj(tooltip, (void*)e); + free (tooltip); obj->explicit_tailtooltip = TRUE; } else if (obj->taillabel) obj->tailtooltip = strdup(obj->taillabel); if ((s = agget(e, "headtooltip")) && s[0]) { - obj->headtooltip = strdup_and_subst_obj(s, (void*)e); + char* tooltip = preprocessTooltip (s, e); + obj->headtooltip = strdup_and_subst_obj(tooltip, (void*)e); + free (tooltip); obj->explicit_headtooltip = TRUE; } else if (obj->headlabel) diff --git a/lib/common/labels.c b/lib/common/labels.c index 78e346221..c6e2ed339 100644 --- a/lib/common/labels.c +++ b/lib/common/labels.c @@ -490,7 +490,8 @@ char *xml_string(char *s) /* xml_string0: * Encode input string as an xml string. * If raw is true, the input is interpreted as having no - * embedded escape sequences. + * embedded escape sequences, and \n and \r are changed + * into and , respectively. * Uses a static buffer, so non-re-entrant. */ char *xml_string0(char *s, boolean raw) @@ -544,6 +545,14 @@ char *xml_string0(char *s, boolean raw) sub = "'"; len = 5; } + else if ((*s == '\n') && raw) { + sub = " "; + len = 5; + } + else if ((*s == '\r') && raw) { + sub = " "; + len = 5; + } else { sub = s; len = 1; diff --git a/plugin/core/gvrender_core_svg.c b/plugin/core/gvrender_core_svg.c index bb2ca7f07..c0cf41174 100644 --- a/plugin/core/gvrender_core_svg.c +++ b/plugin/core/gvrender_core_svg.c @@ -340,7 +340,7 @@ svg_begin_anchor(GVJ_t * job, char *href, char *tooltip, char *target, #endif if (tooltip && tooltip[0]) { gvputs(job, " xlink:title=\""); - gvputs(job, xml_string(tooltip)); + gvputs(job, xml_string0(tooltip, 1)); gvputs(job, "\""); } if (target && target[0]) {