dccec2a514b8c7aeecffbfac722f6c718f204fba
max
  Thu Apr 25 12:08:23 2013 -0700
support for urls in bigBed+ fields via a new tdb statement "urls"
diff --git src/hg/hgc/hgc.c src/hg/hgc/hgc.c
index c32a693..d0cb384 100644
--- src/hg/hgc/hgc.c
+++ src/hg/hgc/hgc.c
@@ -927,46 +927,32 @@
  * return itemName. */
 {
 char *sql = trackDbSetting(tdb, "idInUrlSql");
 char *id = itemName;
 if (sql != NULL)
     {
     char buf[256];
     safef(buf, sizeof(buf), sql, itemName);
     struct sqlConnection *conn = hAllocConn(database);
     id = sqlQuickString(conn, buf);
     hFreeConn(&conn);
     }
 return id;
 }
 
-void printCustomUrlWithLabel(struct trackDb *tdb, char *itemName, char *itemLabel, char *urlSetting, boolean encode)
-/* Print custom URL specified in trackDb settings. */
-{
-char *url;
-char urlLabelSetting[32];
-
-/* check the url setting prefix and get the correct url setting from trackDb */
-if (sameWord(urlSetting, "url"))
-    url = tdb->url;
-else
-    url = trackDbSetting(tdb, urlSetting);
-
-if (url != NULL && url[0] != 0)
-    {
-    char *idInUrl = getIdInUrl(tdb, itemName);
-    if (idInUrl != NULL)
+char* replaceInUrl(struct trackDb *tdb, char *url, char *idInUrl, boolean encode) 
+/* replace $$ in url with idInUrl. Supports many other wildchards */
 	{
 	struct dyString *uUrl = NULL;
 	struct dyString *eUrl = NULL;
 	char startString[64], endString[64];
 	char *ins[9], *outs[9];
 	char *eItem = (encode ? cgiEncode(idInUrl) : cloneString(idInUrl));
 
 	sprintf(startString, "%d", winStart);
 	sprintf(endString, "%d", winEnd);
 	ins[0] = "$$";
 	outs[0] = idInUrl;
 	ins[1] = "$T";
 	outs[1] = tdb->track;
 	ins[2] = "$S";
 	outs[2] = seqName;
@@ -987,53 +973,88 @@
 	    char *nextColon = stringIn(":", suffixClone+1);
 	    if (nextColon)	/* terminate suffixClone suffix */
 		*nextColon = '\0';	/* when next colon is present */
 	    *suffix = '\0';   /* terminate itemClone prefix */
 	    outs[7] = itemClone;
 	    outs[8] = suffixClone;
 	    /* small memory leak here for these cloned strings */
 	    /* not important for a one-time operation in a CGI that will exit */
 	} else {
 	    outs[7] = idInUrl;	/* otherwise, these are not expected */
 	    outs[8] = idInUrl;	/* to be used */
 	}
 	uUrl = subMulti(url, ArraySize(ins), ins, outs);
 	outs[0] = eItem;
 	eUrl = subMulti(url, ArraySize(ins), ins, outs);
+freeDyString(&uUrl);
+freeMem(eItem);
+return eUrl->string;
+}
+
+char* constructUrl(struct trackDb *tdb, char *urlSetting, char *idInUrl, boolean encode) 
+{
+/* construct the url by replacing $$, etc in the url given by urlSetting.
+ * Replace $$ with itemName.  */
+
+// check the url setting prefix and get the correct url setting from trackDb 
+char *url;
+if (sameWord(urlSetting, "url"))
+    url = tdb->url;
+else
+    url = trackDbSetting(tdb, urlSetting);
+
+if (url == NULL || url[0] == 0)
+    return NULL;
+
+char* eUrl = replaceInUrl(tdb, url, idInUrl, encode);
+return eUrl;
+}
+
+void printCustomUrlWithLabel(struct trackDb *tdb, char *itemName, char *itemLabel, char *urlSetting, boolean encode)
+/* Print custom URL specified in trackDb settings. */
+{
+char urlLabelSetting[32];
+
+// first try to resolve itemName via an optional sql statement to something else
+char *idInUrl = getIdInUrl(tdb, itemName);
+if (idInUrl == NULL)
+    return;
+
+// replace the $$ and other wildchards with the url given in tdb 
+char* eUrl = constructUrl(tdb, urlSetting, idInUrl, encode);
+if (eUrl==NULL)
+    return;
+
         /* create the url label setting for trackDb from the url
            setting prefix */
         safef(urlLabelSetting, sizeof(urlLabelSetting), "%sLabel", urlSetting);
         printf("<B>%s </B>",
                trackDbSettingOrDefault(tdb, urlLabelSetting, "Outside Link:"));
-        printf("<A HREF=\"%s\" target=_blank>", eUrl->string);
+printf("<A HREF=\"%s\" target=_blank>", eUrl);
 
 	if (sameWord(tdb->table, "npredGene"))
 	    {
 	    printf("%s (%s)</A><BR>\n", idInUrl, "NCBI MapView");
 	    }
 	else
 	    {
 	    char *label = idInUrl;
 	    if (isNotEmpty(itemLabel) && !sameString(itemName, itemLabel))
 		label = itemLabel;
 	    printf("%s</A><BR>\n", label);
 	    }
-	freeMem(eItem);
-	freeDyString(&uUrl);
-	freeDyString(&eUrl);
-	}
-    }
+//freeMem(&eUrl); small memory leak
 }
 
 void printCustomUrl(struct trackDb *tdb, char *itemName, boolean encode)
 /* Wrapper to call printCustomUrlWithLabel using the url setting in trackDb */
 {
 char urlSetting[10];
 safef(urlSetting, sizeof(urlSetting), "url");
 
 printCustomUrlWithLabel(tdb, itemName, itemName, urlSetting, encode);
 }
 
 void printOtherCustomUrl(struct trackDb *tdb, char *itemName, char* urlSetting, boolean encode)
 /* Wrapper to call printCustomUrlWithLabel to use another url setting other than url in trackDb e.g. url2, this allows the use of multiple urls for a track
  to be set in trackDb. */
 {
@@ -1410,102 +1431,151 @@
 		char dbOnly[4096];
 
 		diff = comp2->start - (comp1->start + comp1->size);
 
 		safef(dbOnly, sizeof(dbOnly), "%s", comp1->src);
 		chopPrefix(dbOnly);
 		printf("%-20s %d\n",hOrganism(dbOnly), diff);
 		}
 
 	    printf("<BR>");
 	    }
         }
     }
 }
 
+struct hash* hashFromString(char* string) 
+/* parse a whitespace-separated string with tuples in the format name=val or
+ * name="val" to a hash name->val */
+{
+if (string==NULL)
+    return NULL;
+
+struct slPair *keyVals = slPairListFromString(string, TRUE);
+if (keyVals==NULL)
+    return NULL;
+
+struct hash *nameToVal = newHash(0);
+struct slPair *kv;
+for (kv = keyVals; kv != NULL; kv = kv->next)
+    hashAdd(nameToVal, kv->name, kv->val);
+return nameToVal;
+}
+
+void printIdOrLinks(struct asColumn *col, struct hash *fieldToUrl, struct trackDb *tdb, char *idList)
+/* if trackDb does not contain a "urls" entry for current column name, just print idList as it is.
+ * Otherwise treat idList as a comma-sep list of IDs and print one row per id, with a link to url,
+ * ($$ in url is OK, wildcards like $P, $p, are also OK)
+ * */
+{
+// try to find a fieldName=url setting in the "urls" tdb statement, print id if not found
+char *url = NULL;
+if (fieldToUrl!=NULL)
+    url = (char*)hashFindVal(fieldToUrl, col->name);
+if ((url==NULL)
+    {
+    printf("<td>%s</td></tr>\n", idList);
+    return;
+    }
+
+// split the id into parts and print each part as a link
+struct slName *slIds = slNameListFromComma(idList);
+struct slName *itemId = NULL;
+printf("<td>");
+for (itemId = slIds; itemId!=NULL; itemId = itemId->next) 
+    {
+    if (itemId!=slIds)
+        printf(", ");
+    char *idUrl = replaceInUrl(tdb, url, itemId->name, TRUE);
+    printf("<a href=\"%s\" target=\"_blank\">%s</a>", idUrl, itemId->name);
+    } 
+printf("</td></tr>\n");
+freeMem(slIds);
+}
+
 int extraFieldsPrint(struct trackDb *tdb,struct sqlResult *sr,char **fields,int fieldCount)
 // Any extra bed or bigBed fields (defined in as and occurring after N in bed N + types.
 // sr may be null for bigBeds.
 // Returns number of extra fields actually printed.
 {
 struct sqlConnection *conn = NULL ;
 if (!trackHubDatabase(database))
     conn = hAllocConnTrack(database, tdb);
 struct asObject *as = asForTdb(conn, tdb);
 hFreeConn(&conn);
 if (as == NULL)
     return 0;
 
 // We are trying to print extra fields so we need to figure out how many fields to skip
 int start = 0;
 char *type = cloneString(tdb->type);
 char *word = nextWord(&type);
 if (word && (sameWord(word,"bed") || sameWord(word,"bigBed")))
     {
     if (NULL != (word = nextWord(&type)))
         start = sqlUnsigned(word);
     else // custom beds and bigBeds may not have full type "begBed 9 +"
         start = max(0,slCount(as->columnList) - fieldCount);
     }
 int count = 0;
 struct asColumn *col = as->columnList;
+char *urlsStr = trackDbSetting(tdb, "urls");
+struct hash* fieldToUrl = hashFromString(urlsStr);
+
 for (;col != NULL && count < fieldCount;col=col->next)
     {
     if (start > 0)  // skip past already known fields
         {
         start--;
         continue;
         }
     int ix = count;
     if (sr != NULL)
         {
         ix = sqlFieldColumn(sr, col->name); // If sr provided, name must match sql columnn name!
         if (ix == -1 || ix > fieldCount)      // so extraField really just provides a label
             continue;
         }
 
     // Print as table rows
     if (count == 0)
         printf("<br><table>");
     count++;
     printf("<tr><td><B>%s:</B></td>", col->comment);
     if (col->isList || col->isArray || col->lowType->stringy)
-        {
-        printf("<td>%s</td></tr>\n", fields[ix]);
-        }
+        printIdOrLinks(col, fieldToUrl, tdb, fields[ix]);
     else if (asTypesIsInt(col->lowType->type))
         {
         long valInt = strtol(fields[ix],NULL,10);
         if (errno == 0 && valInt != 0)
             printf("<td>%ld</td></tr>\n", valInt);
         else
             printf("<td>%s</td></tr>\n", fields[ix]); // decided not to print error
         }
     else if (asTypesIsFloating(col->lowType->type))
         {
         double valDouble = strtod(fields[ix],NULL);
         if (errno == 0 && valDouble != 0)
             printf("<td>%g</td></tr>\n", valDouble);
         else
             printf("<td>%s</td></tr>\n", fields[ix]); // decided not to print error
         }
     else
-        {
         printf("<td>%s</td></tr>\n", fields[ix]);
         }
-    }
 asObjectFree(&as);
+freeMem(fieldToUrl);
 if (count > 0)
     printf("</table>\n");
 
 return count;
 }
 
 void genericBedClick(struct sqlConnection *conn, struct trackDb *tdb,
 		     char *item, int start, int bedSize)
 /* Handle click in generic BED track. */
 {
 char table[64];
 boolean hasBin;
 struct bed *bed;
 char query[512];
 struct sqlResult *sr;