f19df2f83650190a467c3b1226b3e8a23f6d48c0 braney Thu Sep 17 17:04:48 2026 -0700 hgc: build the chain and net details pages with htmlPrintf The chain, snake, quickLift-chain and net click handlers assembled their output with plain printf. Route the values they print -- assembly and organism names, sequence names, positions, the chain id and the quickLiftDb setting -- through htmlPrintf instead, in the four handlers and in the five linkToOtherBrowser* anchor helpers they share. The anchor statements are split rather than converted whole, so that hgTracksName(), cartSidUrlString() and the genark hubUrl stay in a plain printf and only the values go through htmlPrintf. Converting a whole statement rewrites the '/' of ../cgi-bin/hgTracks as an entity on every chain, net, maf and ortho details page; split this way the output is unchanged for ordinary data. Verified against the shared build: the native chainMm39, multiz100way and quickLift "Alignment Differences" pages are byte-identical, and netMm39 differs only in the label "N's", which renders the same. refs #20824 diff --git src/hg/hgc/hgc.c src/hg/hgc/hgc.c index c51d863f3e8..d45e5586048 100644 --- src/hg/hgc/hgc.c +++ src/hg/hgc/hgc.c @@ -3949,77 +3949,79 @@ sqlSafef(query, sizeof(query), "select * from %s where id = %d", table, id); sr = sqlGetResult(conn, query); row = sqlNextRow(sr); if (row == NULL) errAbort("Can't find %d in %s", id, table); chain = chainHeadLoad(row + hasBin); sqlFreeResult(&sr); chainDbAddBlocks(chain, track, conn); return chain; } void linkToOtherBrowserHub(char *otherDb, char *chrom, int start, int end, char *hubUrl) /* Make anchor tag to open another browser window. */ { -printf("", - hgTracksName(), otherDb, chrom, start+1, end, hubUrl); +printf("", hubUrl); } void linkToOtherBrowserExtra(char *otherDb, char *chrom, int start, int end, char *extra) /* Make anchor tag to open another browser window. */ { -printf("", - hgTracksName(), otherDb, extra, chrom, start+1, end); +printf("", otherDb, extra, chrom, start+1, end); } void linkToOtherBrowserSearch(char *otherDb, char *tag) /* Make anchor tag to open another browser window. */ { -printf("", - hgTracksName(), otherDb, tag); +printf("", otherDb, tag); } void linkToOtherBrowser(char *otherDb, char *chrom, int start, int end) /* Make anchor tag to open another browser window. */ { -printf("", - hgTracksName(), otherDb, chrom, start+1, end); +printf("", otherDb, chrom, start+1, end); } void linkToOtherBrowserTitle(char *otherDb, char *chrom, int start, int end, char *title) /* Make anchor tag to open another browser window. */ { -printf("", - title, hgTracksName(), otherDb, chrom, start+1, end); +htmlPrintf("", otherDb, chrom, start+1, end); } void chainToOtherBrowser(struct chain *chain, char *otherDb, char *otherOrg, char *hubUrl) /* Put up link that lets us use chain to browser on * corresponding window of other species. */ { struct chain *subChain = NULL, *toFree = NULL; int qs,qe; chainSubsetOnT(chain, winStart, winEnd, &subChain, &toFree); if (subChain != NULL && otherOrg != NULL) { qChainRangePlusStrand(subChain, &qs, &qe); if (hubUrl) linkToOtherBrowserHub(otherDb, subChain->qName, qs-1, qe, hubUrl); else linkToOtherBrowser(otherDb, subChain->qName, qs-1, qe); - printf("Open %s browser at position corresponding to the part of chain that is in this window.
\n", trackHubSkipHubName(otherOrg)); + htmlPrintf("Open %s browser at position corresponding to the part of chain that is in this window.
\n", trackHubSkipHubName(otherOrg)); } chainFree(&toFree); } struct dnaSeq *otherChromSeq(struct twoBitFile *otherTbf, char *otherDb, char *otherChrom, int otherStart, int otherEnd) /* This fetches sequence from tdb->otherTwoBitUrl if available, if not * tries to find it via otherDb */ { if (otherTbf != NULL) { return twoBitReadSeqFrag(otherTbf, otherChrom, otherStart, otherEnd); } else { @@ -4038,41 +4040,47 @@ char *aliasQName = qName; boolean otherIsActive = FALSE; char *hubUrl = NULL; if (hDbIsActive(otherDb)) otherIsActive = TRUE; else hubUrl = genarkUrl(otherDb); // may be NULL char headerText[256]; safef(headerText, sizeof headerText, "reference: %s, query: %s\n", trackHubSkipHubName(database), trackHubSkipHubName(otherDb) ); genericHeader(parentTdb, headerText); if (hubUrl != NULL) - printf("%s:%d-%d link to block in query assembly: %s
\n", hubUrl, otherDb, aliasQName, qs, qe, aliasQName, qs, qe, trackHubSkipHubName(otherDb)); + { + printf("%s:%d-%d link to block in query assembly: %s
\n", otherDb, aliasQName, qs, qe, aliasQName, qs, qe, trackHubSkipHubName(otherDb)); + } else if (otherIsActive) - printf("%s:%d-%d link to block in query assembly: %s
\n", otherDb, aliasQName, qs, qe, aliasQName, qs, qe, trackHubSkipHubName(otherDb)); + htmlPrintf("%s:%d-%d link to block in query assembly: %s
\n", otherDb, aliasQName, qs, qe, aliasQName, qs, qe, trackHubSkipHubName(otherDb)); int qCenter = (qs + qe) / 2; int newQs = qCenter - qWidth/2; int newQe = qCenter + qWidth/2; if (hubUrl != NULL) - printf("%s:%d-%d link to same window size in query assembly: %s
\n", hubUrl,otherDb, aliasQName, newQs, newQe,aliasQName, newQs, newQe, trackHubSkipHubName(otherDb) ); + { + printf("%s:%d-%d link to same window size in query assembly: %s
\n", otherDb, aliasQName, newQs, newQe,aliasQName, newQs, newQe, trackHubSkipHubName(otherDb) ); + } else if (otherIsActive) - printf("%s:%d-%d link to same window size in query assembly: %s
\n", otherDb, aliasQName, newQs, newQe,aliasQName, newQs, newQe, trackHubSkipHubName(otherDb) ); + htmlPrintf("%s:%d-%d link to same window size in query assembly: %s
\n", otherDb, aliasQName, newQs, newQe,aliasQName, newQs, newQe, trackHubSkipHubName(otherDb) ); printTrackHtml(tdb); } void genericChainClick(struct sqlConnection *conn, struct trackDb *tdb, char *item, int start, char *otherDb) /* Handle click in chain track, at least the basics. */ { boolean doSnake = cartOrTdbBoolean(cart, tdb, "doSnake" , FALSE) && cfgOptionBooleanDefault("canSnake", TRUE); if (doSnake) return doSnakeChainClick(tdb, item, otherDb); struct twoBitFile *otherTbf = getOtherTwoBitUrl(tdb); char *thisOrg = hOrganism(database); char *otherOrg = NULL; @@ -4145,59 +4153,65 @@ atoi(words[0])); } } if (subChain->qStrand == '-') reverseIntRange(&qStart, &qEnd, subChain->qSize); qSeq = otherChromSeq(otherTbf, otherDb, subChain->qName, qStart, qEnd); if (subChain->qStrand == '-') reverseComplement(qSeq->dna, qSeq->size); subChain->score = chainCalcScoreSubChain(subChain, scoreScheme, gapCalc, qSeq, tSeq); subSetScore = subChain->score; } chainFree(&toFree); -printf("%s position: %s:%d-%d" - " size: %d
\n", - trackHubSkipHubName(thisOrg), hgTracksName(), cartSidUrlString(cart), database, - chain->tName, chain->tStart+1, chain->tEnd, chain->tName, chain->tStart+1, chain->tEnd, - chain->tEnd-chain->tStart); +htmlPrintf("%s position: ", trackHubSkipHubName(thisOrg)); +printf("%s:%d-%d size: %d
\n", + database, chain->tName, chain->tStart+1, chain->tEnd, + chain->tName, chain->tStart+1, chain->tEnd, chain->tEnd-chain->tStart); printf("Strand: %c
\n", chain->qStrand); qChainRangePlusStrand(chain, &qs, &qe); if (sameWord(otherDb, "seq")) { - printf("%s position: %s:%d-%d size: %d
\n", + htmlPrintf("%s position: %s:%d-%d size: %d
\n", otherOrg, chain->qName, qs, qe, chain->qEnd - chain->qStart); } else { /* prints link to other db browser only if db exists and is active */ /* else just print position with no link for the other db */ - printf("%s position: ", otherOrg); + htmlPrintf("%s position: ", otherOrg); if (otherIsActive) - printf(" ", - hgTracksName(), otherDb, chain->qName, qs, qe); + { + printf(" ", otherDb, chain->qName, qs, qe); + } else if (hubUrl != NULL) - printf(" ", - hgTracksName(), otherDb, hubUrl, chain->qName, qs, qe); - printf("%s:%d-%d", chain->qName, qs, qe); + { + printf(" ", chain->qName, qs, qe); + } + htmlPrintf("%s:%d-%d", chain->qName, qs, qe); if (otherIsActive || hubUrl) printf(""); printf(" size: %d
\n", chain->qEnd - chain->qStart); } -printf("Chain ID: %s
\n", item); +htmlPrintf("Chain ID: %s
\n", item); printf("Score: %1.0f\n", chain->score); if (nullSubset) printf("Score within browser window: N/A (no aligned bases)
\n"); else if (otherIsActive && subChain != chain) printf("  Approximate Score within browser window: %1.0f
\n", subSetScore); else printf("
\n"); boolean normScoreAvailable = chainDbNormScoreAvailable(tdb); // The normalized score lives in the chain table, so for a quickLifted track it has to be // read from the assembly the chain came from. Against the assembly on screen the table // name either does not resolve, or resolves to a same-named table there and returns @@ -4227,31 +4241,31 @@ if ((row = sqlNextRow(sr)) != NULL) { double normScore = atof(row[0]); int basesAligned = chain->score / normScore; printf("Normalized Score: %1.0f (aligned bases: %d)", normScore, basesAligned); } sqlFreeResult(&sr); printf("
\n"); } if (lifted) hFreeConn(&normConn); if (quickLiftIsLifted(tdb) && !quickLiftIsOwnChainTrack(tdb)) // A lifted chain is only worked out over the window being viewed, so the whole chain's // extent is not knowable here and the usual sentence would be wrong. - printf("
This chain comes from %s and is mapped onto %s as the browser draws it, so " + htmlPrintf("
This chain comes from %s and is mapped onto %s as the browser draws it, so " "the fields above describe the part of it around the window rather than the " "whole chain.
\n", trackDbSetting(tdb, "quickLiftDb"), trackHubSkipHubName(database)); else printf("
Fields above refer to entire chain or gap, not just the part inside the window.
\n"); printf("
\n"); chainWinSize = min(winEnd-winStart, chain->tEnd - chain->tStart); /* Show alignment if the database exists and */ /* if there is a chromInfo table for that database and the sequence */ /* file exists. This means that alignments can be shown on the archive */ /* server (or in other cases) if there is a database with a chromInfo table, */ /* the sequences are available and there is an entry added to dbDb for */ /* the otherDb. */ if (otherTbf != NULL || @@ -4325,42 +4339,42 @@ struct dnaSeq *loadGenomePart(char *db, char *chrom, int start, int end) /* Load genomic dna from given database and position. */ { char nibFile[512]; findNib(db, chrom, nibFile); return hFetchSeq(nibFile, chrom, start, end); } void printLabeledNumber(char *org, char *label, long long number) /* Print label: in bold face, and number with commas. */ { char *space = " "; if (org == NULL) org = space = ""; -printf("%s%s%s: ", org, space, label); +htmlPrintf("%s%s%s: ", org, space, label); printLongWithCommas(stdout, number); printf("
\n"); } void printLabeledPercent(char *org, char *label, long p, long q) /* Print label: in bold, then p, and then 100 * p/q */ { char *space = " "; if (org == NULL) org = space = ""; -printf("%s%s%s: ", org, space, label); +htmlPrintf("%s%s%s: ", org, space, label); printLongWithCommas(stdout, p); if (q != 0) printf(" (%3.1f%%)", 100.0 * p / q); printf("
\n"); } static struct netAlign *bigNetLoadOne(struct trackDb *tdb, char *chrom, int start, unsigned level, boolean *retClipped) /* Load the record from a bigNet file at the given level that covers start. * Returns NULL if there isn't one. Sets *retClipped when a quickLifted row would * only lift with its ends pulled in, so the extent reported is the visible part * rather than the whole item. */ { char *fileName = hReplaceGbdb(trackDbSetting(tdb, "bigDataUrl")); char *quickLiftFile = trackDbSetting(tdb, "quickLiftUrl"); @@ -4574,49 +4588,49 @@ if (chain != NULL) { /* print link to browser for otherDb only if otherDb is active */ if (hDbIsActive(otherDb)) chainToOtherBrowser(chain, otherDb, otherOrgBrowser, NULL); chainFree(&chain); } htmlHorizontalLine(); } else if (net->chainId != 0) { /* Only an isBig track with no chain track to follow gets here. */ if (isLifted) { char *sourceDb = trackDbSetting(tdb, "quickLiftDb"); - printf("
This net was lifted from %s, so its chains are not on this assembly " + htmlPrintf("
This net was lifted from %s, so its chains are not on this assembly " "and the alignment cannot be shown here.
\n", isEmpty(sourceDb) ? "another assembly" : sourceDb); } else { /* A hub whose type line names a chain track this assembly does not have. Say so * rather than dropping the whole section without a word. */ htmlPrintf("
This track's type line names the chain track %s, which is not on " "this assembly, so the alignment cannot be shown here.
\n", emptyForNull(namedChainTrack)); } htmlHorizontalLine(); } -printf("Type: %s
\n", net->type); +htmlPrintf("Type: %s
\n", net->type); printf("Level: %d
\n", (net->level+1)/2); -printf("%s position: %s:%d-%d
\n", +htmlPrintf("%s position: %s:%d-%d
\n", org, net->tName, net->tStart+1, net->tEnd); -printf("%s position: %s:%d-%d
\n", +htmlPrintf("%s position: %s:%d-%d
\n", otherOrg, net->qName, net->qStart+1, net->qEnd); printf("Strand: %c
\n", net->strand[0]); printLabeledNumber(NULL, "Score", net->score); if (net->chainId) { printf("Chain ID: %u
\n", net->chainId); printLabeledNumber(NULL, "Bases aligning", net->ali); if (net->qOver >= 0) printLabeledNumber(otherOrg, "parent overlap", net->qOver); if (net->qFar >= 0) printLabeledNumber(otherOrg, "parent distance", net->qFar); if (net->qDup >= 0) printLabeledNumber(otherOrg, "bases duplicated", net->qDup); } if (net->tN >= 0) @@ -5194,39 +5208,39 @@ otherIsActive = TRUE; otherDb = genome->name; } } } chainSubsetOnT(chain, winStart, winEnd, &subChain, &toFree); char position[128]; char *ourPos, *otherPos; int seqStart = cartInt(cart, "l"); int seqEnd = cartInt(cart, "r"); char *chromName = cartString(cart, "c"); safef(position, 128, "%s:%d-%d", chromName, seqStart, seqEnd); ourPos = cloneString(addCommasToPos(database, position)); -printf("%s position: %s
", trackHubSkipHubName(database), ourPos); +htmlPrintf("%s position: %s
", trackHubSkipHubName(database), ourPos); int qs,qe; qChainRangePlusStrand(subChain, &qs, &qe); safef(position, 128, "%s:%d-%d", subChain->qName, qs-1, qe); otherPos = cloneString(addCommasToPos(otherDb, position)); -printf("%s position: ", trackHubSkipHubName(otherDb)); +htmlPrintf("%s position: ", trackHubSkipHubName(otherDb)); linkToOtherBrowser(otherDb, subChain->qName, qs-1, qe); -printf("%s

", otherPos); +htmlPrintf("%s

", otherPos); chainWinSize = min(winEnd-winStart, chain->tEnd - chain->tStart); if (otherTbf != NULL || (!startsWith("big", tdb->type) && sqlDatabaseExists(otherDb) && chromSeqFileExists(otherDb, chain->qName))) { if (chainWinSize < 1000000) { printf("View "); hgcAnchorSomewhere("htcChainAli", item, tdb->track, chain->tName); printf("DNA sequence alignment of whole window.

"); } else { printf("Zoom so that browser window covers 1,000,000 bases or less " @@ -5259,115 +5273,123 @@ case QUICKTYPE_MISMATCH: mismatches++; break; } } // If the click came from a specific difference (not one of the "identical" // regions), the matching row is shown in bold in the tables below. if (clickStart >= 0) printf("
The item you clicked on is shown in bold in the tables below.
\n"); if (deletions) { printf("
Deletions in Window:
"); printf("\n"); - printf("", trackHubSkipHubName(database), trackHubSkipHubName(otherDb)); + htmlPrintf("", trackHubSkipHubName(database), trackHubSkipHubName(otherDb)); for(hr = regions; hr; hr = hr->next) { if (hr->type != QUICKTYPE_DEL) continue; char *ourPos, *otherPos; snprintf(position, 128, "%s:%ld-%ld", hr->chrom, hr->chromStart, hr->chromEnd); ourPos = cloneString(addCommasToPos(database, position)); snprintf(position, 128, "%s:%ld-%ld", hr->oChrom, hr->oChromStart, hr->oChromEnd); otherPos = cloneString(addCommasToPos(database, position)); char *hilite = (clickStart >= 0 && hr->chromStart == clickStart && hr->chromEnd == clickEnd) ? " style='font-weight:bold'" : ""; - printf("", ourPos, otherPos); + printf(""); } printf("
%s Position%s PositionBasesAlignment
%s Position%s PositionBasesAlignment
%s%s%.*s", hilite, ourPos, otherPos, hr->otherBaseCount, hr->otherBases); + printf("", hilite); + htmlPrintf("%s%s%.*s", hr->otherBaseCount, hr->otherBases); hgcAnchorSomewhereExt("htcChainAli", item, tdb->track, chain->tName, hr->chromStart - 10, hr->chromEnd + 10, tdb->track); printf("alignment
"); } if (insertions) { printf("
Insertions in Window:
"); printf("\n"); - printf("", trackHubSkipHubName(database), trackHubSkipHubName(otherDb)); + htmlPrintf("", trackHubSkipHubName(database), trackHubSkipHubName(otherDb)); for(hr = regions; hr; hr = hr->next) { if (hr->type != QUICKTYPE_INSERT) continue; char *ourPos, *otherPos; snprintf(position, 128, "%s:%ld-%ld", hr->chrom, hr->chromStart, hr->chromEnd); ourPos = cloneString(addCommasToPos(database, position)); snprintf(position, 128, "%s:%ld-%ld", hr->oChrom, hr->oChromStart, hr->oChromEnd); otherPos = cloneString(addCommasToPos(database, position)); char *hilite = (clickStart >= 0 && hr->chromStart == clickStart && hr->chromEnd == clickEnd) ? " style='font-weight:bold'" : ""; - printf("", ourPos, otherPos); + printf(""); } printf("
%s Position%s PositionBasesAlignment
%s Position%s PositionBasesAlignment
%s%s%.*s", hilite, ourPos, otherPos, hr->baseCount, hr->bases); + printf("", hilite); + htmlPrintf("%s%s%.*s", hr->baseCount, hr->bases); hgcAnchorSomewhereExt("htcChainAli", item, tdb->track, chain->tName, hr->chromStart - 10, hr->chromEnd + 10, tdb->track); printf("alignment
"); } if (doubles) { printf("
Double Gaps in Window:
"); printf("\n"); - printf("", trackHubSkipHubName(database), trackHubSkipHubName(otherDb), trackHubSkipHubName(database), trackHubSkipHubName(otherDb)); + htmlPrintf("", trackHubSkipHubName(database), trackHubSkipHubName(otherDb), trackHubSkipHubName(database), trackHubSkipHubName(otherDb)); for(hr = regions; hr; hr = hr->next) { if (hr->type != QUICKTYPE_DOUBLE) continue; char *ourPos, *otherPos; snprintf(position, 128, "%s:%ld-%ld", hr->chrom, hr->chromStart, hr->chromEnd); ourPos = cloneString(addCommasToPos(database, position)); snprintf(position, 128, "%s:%ld-%ld", hr->oChrom, hr->oChromStart, hr->oChromEnd); otherPos = cloneString(addCommasToPos(database, position)); char *hilite = (clickStart >= 0 && hr->chromStart == clickStart && hr->chromEnd == clickEnd) ? " style='font-weight:bold'" : ""; - printf("", ourPos, otherPos); + printf(""); } printf("
%s Position%s Position# Bases in %s#Bases in %sAlignment
%s Position%s Position# Bases in %s#Bases in %sAlignment
%s%s%d%d", hilite, ourPos, otherPos, hr->baseCount, hr->otherBaseCount); + printf("", hilite); + htmlPrintf("%s%s%d%d", hr->baseCount, hr->otherBaseCount); hgcAnchorSomewhereExt("htcChainAli", item, tdb->track, chain->tName, hr->chromStart - 10, hr->chromEnd + 10, tdb->track); printf("alignment
"); } if (mismatches) { printf("
Mismatches in Window:
"); printf("\n"); - printf("", trackHubSkipHubName(database), trackHubSkipHubName(otherDb)); + htmlPrintf("", trackHubSkipHubName(database), trackHubSkipHubName(otherDb)); for(hr = regions; hr; hr = hr->next) { if (hr->type != QUICKTYPE_MISMATCH) continue; char *ourPos, *otherPos; snprintf(position, 128, "%s:%ld-%ld", hr->chrom, hr->chromStart, hr->chromEnd); ourPos = cloneString(addCommasToPos(database, position)); snprintf(position, 128, "%s:%ld-%ld", hr->oChrom, hr->oChromStart, hr->oChromEnd); otherPos = cloneString(addCommasToPos(database, position)); char *hilite = (clickStart >= 0 && hr->chromStart == clickStart && hr->chromEnd == clickEnd) ? " style='font-weight:bold'" : ""; - printf("", ourPos, otherPos); + printf(""); } printf("
%s Position%s PositionChangeAlignment
%s Position%s PositionChangeAlignment
%s%s%.*s -> %.*s", hilite, ourPos, otherPos, hr->otherBaseCount, hr->otherBases, hr->baseCount, hr->bases); + printf("", hilite); + htmlPrintf("%s%s%.*s -> %.*s", hr->otherBaseCount, hr->otherBases, hr->baseCount, hr->bases); hgcAnchorSomewhereExt("htcChainAli", item, tdb->track, chain->tName, hr->chromStart - 10, hr->chromEnd + 10, tdb->track); printf("alignment
"); } } void genericClickHandlerPlus( struct trackDb *tdb, char *item, char *itemForUrl, char *plus) /* Put up generic track info, with additional text appended after item. */ { char *dupe, *type, *words[16], *headerItem; int wordCount; int start = cartInt(cart, "o"); int end = cartInt(cart, "t");