c0e8fa6df3a0bd406c4188d49ee00f20aef203e5 max Mon Sep 7 12:07:18 2026 -0700 Substitute trackDb variables in hub track description pages A hub's description page comes straight off the hub's web server and has never been through variable substitution, so a $db or $parentTrack in it reached the reader as literal text. Native trackDb pages are fine, since hgTrackDb substitutes them when it loads trackDb, but there was no equivalent step for a hub. hgc's getTrackHtml and hgTrackUi's trackUi both call hVarSubstTrackDbHtml on a hub track's html. Only a short list of variables is recognized there and nothing is an error, because a hub page written before this existed can easily contain a dollar sign inside a shell example, and silently rewriting that would be worse than not substituting at all. Adds $parentTrack, the name of the container a track sits in, which is what a subtrack description page needs to link back to its superTrack or composite. Views are skipped, since a view has no page of its own, and the hub_<id>_ prefix is kept so the name works as hgTrackUi's g= parameter. Documents $track, $parentTrack and $hgsid in trackDb/README. refs #37599 diff --git src/hg/lib/hVarSubst.c src/hg/lib/hVarSubst.c index 07a96decb4c..d471025f201 100644 --- src/hg/lib/hVarSubst.c +++ src/hg/lib/hVarSubst.c @@ -1,27 +1,28 @@ /* Handle variable substitutions in strings from trackDb and other * labels. See trackDb/README for descriptions of values that can be * substitute. This code needs to do a special handle to remain compatibility * with behavior of the old substitution mechanism. */ /* Copyright (C) 2014 The Regents of the University of California * See kent/LICENSE or http://genome.ucsc.edu/license/ for licensing information. */ #include "common.h" #include "trackDb.h" #include "hdb.h" #include "hui.h" #include "sqlNum.h" +#include "hubConnect.h" #include "hVarSubst.h" static boolean isVarEnd(boolean inBraces, char c) /** does the character end a variable reference. */ { if (inBraces) return (c == '}'); else return !((c == '_') || isalnum(c)); } static char *parseVarName(char *desc, char *varStart, char *varName, int varNameSize) /* parse substitution variable name out of a string, returning next position * after name. */ { @@ -33,30 +34,54 @@ p++; int nameIdx = 0; while ((nameIdx < varNameSize-1) && !isVarEnd(inBraces, *p)) varName[nameIdx++] = *p++; if (nameIdx == 0) errAbort("empty variable name in %s varStart='%s' varName='%s'", desc, varStart, varName); if (nameIdx == varNameSize) errAbort("variable name in desc %s exceeds maximum length of %d, starting with: \"%.*s\"", desc, varNameSize-1, varNameSize-1, varName); varName[nameIdx] = '\0'; if (inBraces) p++; return p; } +static char *parseVarNameMaybe(char *varStart, char *varName, int varNameSize) +/* Like parseVarName, but return NULL instead of aborting when what follows the `$' is not + * a well formed variable reference. Used in hubHtml mode, where a stray dollar sign in a + * description page has to survive untouched. */ +{ +char *p = varStart+1; +boolean inBraces = (*p == '{'); +if (inBraces) + p++; +int nameIdx = 0; +while ((nameIdx < varNameSize-1) && (*p != '\0') && !isVarEnd(inBraces, *p)) + varName[nameIdx++] = *p++; +if ((nameIdx == 0) || (nameIdx == varNameSize-1)) + return NULL; +varName[nameIdx] = '\0'; +if (inBraces) + { + if (*p != '}') + return NULL; + p++; + } +return p; +} + static char *lookupTrackDbSubVar(char *desc, struct trackDb *tdb, char *settingName, char *varName) /* get the specified track setting to substitute or die trying; more useful * message than trackDbRequiredSetting when doing substitution */ { char *val = trackDbSettingClosestToHome(tdb, settingName); if (val == NULL) errAbort("trackDb (%s) setting \"%s\" not found for variable substitution of \"$%s\" in %s", tdb->track, settingName, varName, desc); return val; } static char *lookupOtherDb(char *desc, struct trackDb *tdb, char *varName) /* look up the otherDb variable, which is needed for substituting varName */ { return lookupTrackDbSubVar(desc, tdb, "otherDb", varName); @@ -215,129 +240,203 @@ dyStringAppend(dest, org); freeMem(org); } else if (sameString(varBase, "date")) { char *date = valOrDb(hFreezeDateOpt(database), database); dyStringAppend(dest, date); freeMem(date); } else if (sameString(varBase, "db")) dyStringAppend(dest, database); else if (sameString(varBase, "hgsid") && cart != NULL) dyStringAppend(dest, cartSessionId(cart)); } +static char *parentTrackName(struct trackDb *tdb) +/* Name of the container holding tdb, in the form hgTrackUi's g= parameter needs: with the + * hub_<id>_ prefix when this is a hub track, since that is what trackHubAddNamePrefix put + * into tdb->track. Views are skipped, a view has no description page of its own. Returns + * the track's own name when it is not in a container. */ +{ +struct trackDb *parent = tdb->parent; +char *viewName = NULL; +while ((parent != NULL) && tdbIsView(parent, &viewName)) + parent = parent->parent; +return (parent != NULL) ? parent->track : tdb->track; +} + +/* The variables a hub's description page may use. Deliberately a short explicit list and + * not every trackDb setting the way native trackDb allows: a hub page written before this + * substitution existed can easily contain something like "$track" inside a shell example, + * and silently rewriting that would be worse than not substituting at all. */ +static char *hubHtmlVars[] = {"db", "hgsid", "track", "parentTrack", + "organism", "Organism", "ORGANISM", "date", "downloadsServer"}; + +static boolean isHubHtmlVar(struct cart *cart, struct trackDb *tdb, char *varName) +/* Is varName one of the variables a hub description page may use, and can this call + * resolve it? Asked only in hubHtml mode, to tell a variable reference from a dollar sign + * that happens to be followed by a word. */ +{ +if (tdb == NULL) + return FALSE; +if (sameString(varName, "hgsid") && (cart == NULL)) + return FALSE; +int i; +for (i = 0; i < ArraySize(hubHtmlVars); i++) + if (sameString(varName, hubHtmlVars[i])) + return TRUE; +return FALSE; +} + static void substTrackDbVar(char *desc, struct trackDb *tdb, char *database, char *varName, struct dyString *dest) /* substitute a variable value obtained from trackDb */ { if (sameString(varName, "matrix")) substMatrixHtml(tdb, dest); else if (sameString(varName, "chainLinearGap")) substLinearGap(tdb, dest); else if (sameString(varName, "downloadsServer")) dyStringAppend(dest, hDownloadsServer()); else if (sameString(varName, "track")) dyStringAppend(dest, tdb->track); +else if (sameString(varName, "parentTrack")) + dyStringAppend(dest, parentTrackName(tdb)); else dyStringAppend(dest, lookupTrackDbSubVar(desc, tdb, varName, varName)); } static void substVar(char *desc, struct cart *cart, struct trackDb *tdb, char *database, char *varName, struct dyString *dest) /* look up varName and insert value in output string. Error if variable * can't be found */ { if (isDatabaseVar(varName)) substDatabaseVar(database, cart, varName, dest); else if (tdb == NULL) errAbort("invalid variable \"%s\" to substitute in %s", varName, desc); else if (startsWith("o_", varName) && isDatabaseVar(varName+2)) substDatabaseVar(lookupOtherDb(desc, tdb, varName), cart, varName+2, dest); else substTrackDbVar(desc, tdb, database, varName, dest); } static char *hVarSubstExt(char *desc, struct cart *cart, struct trackDb *tdb, char *database, - char *src) + char *src, boolean hubHtml) /* Parse a string and substitute variable references. Return NULL if * no variable references were found. Error on missing variables (except * $matrix). desc is a brief description to print on an error to help with * debugging. tdb maybe NULL to only do substitutions based on database - * and organism. cart may be NULL. See trackDb/README for more information.*/ + * and organism. cart may be NULL. See trackDb/README for more information. + * In hubHtml mode nothing is an error and only the variables in hubHtmlVars are + * recognized: every other `$' is copied through unchanged. */ { struct dyString *dest = NULL; char *start = src; // start of current static string in src char *next = src; // cursor char varName[65]; while ((next = strchr(next, '$')) != NULL) { if (dest == NULL) dest = dyStringNew(strlen(src)); dyStringAppendN(dest, start, next-start); if (*(next+1) == '$') { // $$ is a literal $ dyStringAppendC(dest, '$'); start = next = next + 2; } + else if (hubHtml) + { + // variable reference, or just a dollar sign in the text + char *after = parseVarNameMaybe(next, varName, sizeof(varName)); + if ((after != NULL) && isHubHtmlVar(cart, tdb, varName)) + { + substVar(desc, cart, tdb, database, varName, dest); + start = next = after; + } + else + { + dyStringAppendC(dest, '$'); + start = next = next + 1; + } + } else { // variable reference start = next = parseVarName(desc, next, varName, sizeof(varName)); substVar(desc, cart, tdb, database, varName, dest); } } if (dest != NULL) { dyStringAppend(dest, start); return dyStringCannibalize(&dest); } else return NULL; // no substitutions } char *hVarSubst(char *desc, struct trackDb *tdb, char *database, char *src) /* Parse a string and substitute variable references. Return NULL if * no variable references were found. Error on missing variables (except * $matrix). desc is a brief description to print on error to help with * debugging. tdb maybe NULL to only do substitutions based on database * and organism. See trackDb/README for more information.*/ { -return hVarSubstExt(desc, NULL, tdb, database, src); +return hVarSubstExt(desc, NULL, tdb, database, src, FALSE); } void hVarSubstInVar(char *desc, struct trackDb *tdb, char *database, char **varPtr) /* hVarSubst on a dynamically allocated string, replacing string in substitutions * occur, freeing the old memory if necessary. See hVarSubst for details. */ { -char *dest = hVarSubstExt(desc, NULL, tdb, database, *varPtr); +char *dest = hVarSubstExt(desc, NULL, tdb, database, *varPtr, FALSE); if (dest != NULL) { freez(varPtr); *varPtr = dest; } } void hVarSubstTrackDb(struct trackDb *tdb, char *database) /* Substitute variables in trackDb shortLabel, longLabel, and html fields. */ { hVarSubstInVar(tdb->track, tdb, database, &tdb->shortLabel); hVarSubstInVar(tdb->track, tdb, database, &tdb->longLabel); hVarSubstInVar(tdb->track, tdb, database, &tdb->html); } void hVarSubstWithCart(char *desc, struct cart *cart, struct trackDb *tdb, char *database, char **varPtr) /* Like hVarSubstInVar, but if cart is non-NULL, $hgsid will be substituted. */ { -char *dest = hVarSubstExt(desc, cart, tdb, database, *varPtr); +char *dest = hVarSubstExt(desc, cart, tdb, database, *varPtr, FALSE); if (dest != NULL) { freez(varPtr); *varPtr = dest; } } + +void hVarSubstTrackDbHtml(struct cart *cart, struct trackDb *tdb, char *database) +/* Substitute variables in the description page of a hub track. Native trackDb needs no + * such call: hgTrackDb already substituted the html when it loaded trackDb. A hub's html + * comes straight off the hub's web server and has never been through substitution, so it + * is done here, at render time, where $db, $hgsid and $parentTrack resolve to the hub_<id>_ + * names the CGIs actually use. Only the variables in hubHtmlVars are recognized and + * nothing is an error, so a dollar sign in a description page that was not written with + * this in mind stays a dollar sign. */ +{ +if ((tdb == NULL) || isEmpty(tdb->html) || !isHubTrack(tdb->track)) + return; +char *dest = hVarSubstExt(tdb->track, cart, tdb, database, tdb->html, TRUE); +if (dest != NULL) + { + freez(&tdb->html); + tdb->html = dest; + } +}