]> granicus.if.org Git - graphviz/commitdiff
Fix tooltips to support line breaks and escString substitutions
authorEmden R. Gansner <erg@gentoo.local>
Sun, 7 Jun 2015 19:12:48 +0000 (15:12 -0400)
committerEmden R. Gansner <erg@gentoo.local>
Sun, 7 Jun 2015 19:12:48 +0000 (15:12 -0400)
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.

lib/common/emit.c
lib/common/labels.c
plugin/core/gvrender_core_svg.c

index cc2744aa013ec3875b861803c1711fd3eb5e9522..b8af0a639e7ca54ada6c325604433ce83e550225 100644 (file)
@@ -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)
index 78e34622185955fc8f34e4b79732ba498b4b5e36..c6e2ed339394b9fb1423c08c492d45300bb2c9ca 100644 (file)
@@ -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 &#10; and &#13;, 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 = "&#39;";
            len = 5;
        }
+       else if ((*s == '\n') && raw) {
+           sub = "&#10;";
+           len = 5;
+       }
+       else if ((*s == '\r') && raw) {
+           sub = "&#13;";
+           len = 5;
+       }
        else {
            sub = s;
            len = 1;
index bb2ca7f07bd8954f18d7000984c80df2e1271f4d..c0cf411746a317d50e9af7511d10b6df6133cffd 100644 (file)
@@ -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]) {