e6b0dc7877ae4b24f56682885e03263fd2da3582 braney Tue Aug 18 11:37:04 2026 -0700 lib, hgc: encode barChart, BAM and VCF detail text consistently, refs #38123 diff --git src/hg/hgc/vcfClick.c src/hg/hgc/vcfClick.c index eaabf39d65d..aa07282f957 100644 --- src/hg/hgc/vcfClick.c +++ src/hg/hgc/vcfClick.c @@ -1,27 +1,28 @@ /* vcfTrack -- handlers for Variant Call Format data. */ /* 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 "dystring.h" #include "errCatch.h" #include "hCommon.h" #include "hdb.h" #include "hgc.h" #include "htmshell.h" +#include "htmshell.h" #include "jsHelper.h" #include "pgSnp.h" #include "regexHelper.h" #include "trashDir.h" #include "knetUdc.h" #include "udc.h" #include "vcf.h" #include "vcfUi.h" #include "trackHub.h" #include "featureBits.h" #define NA "<em>n/a</em>" static void printKeysWithDescriptions(struct vcfFile *vcff, int wordCount, char **words, struct vcfInfoDef *infoDefs, boolean stripToSymbol) @@ -90,148 +91,168 @@ printf("<B>Filter:</B> "NA"<BR>\n"); else if (rec->filterCount == 1 && sameString(rec->filters[0], "PASS")) printf("<B>Filter:</B> PASS<BR>\n"); else { printf("<B>Filter failures:</B> "); printf("<font style='font-weight: bold; color: #FF0000;'>\n"); struct vcfFile *vcff = rec->file; printKeysWithDescriptions(vcff, rec->filterCount, rec->filters, vcff->filterDefs, FALSE); printf("</font>\n"); } } -static int printTabularHeaderRow(const struct vcfInfoDef *def) +static int printTabularHeaderRow(struct trackDb *tdb, const struct vcfInfoDef *def) /* Parse the column header parts out of def->description and print as table header row; * call this only when looksTabular returns TRUE. * Returns the number of columns in the header */ { regmatch_t substrArr[PATH_LEN]; if (regexMatchSubstr(def->description, COL_DESC_REGEX, substrArr, ArraySize(substrArr))) { puts("<TR>"); // Make a copy of the part of def->description that matches the regex, // then chop by '|' and print out header column tags: int matchSize = substrArr[0].rm_eo - substrArr[0].rm_so; char copy[matchSize+1]; safencpy(copy, sizeof(copy), def->description + substrArr[0].rm_so, matchSize); // Turn '_' into ' ' so description words can wrap inside headers, saving some space subChar(copy, '_', ' '); char *words[PATH_LEN]; int descColCount = chopByChar(copy, '|', words, ArraySize(words)); int i; for (i = 0; i < descColCount; i++) - printf("<TH class='withThinBorder'>%s</TH>", words[i]); + printf("<TH class='withThinBorder'>%s</TH>", hubEncode(tdb, words[i])); puts("</TR>"); return descColCount; } else errAbort("printTabularHeaderRow: code bug, if looksTabular returns true then " "regex should work here"); return -1; } -static void printTabularData(struct vcfInfoElement *el, int headerCount) +static void printTabularData(struct trackDb *tdb, struct vcfInfoElement *el, int headerCount) /* Print a row for each value in el, separating columns by '|'. */ { int j; for (j = 0; j < el->count; j++) { puts("<TR>"); char *val = el->values[j].datString; if (!isEmpty(val)) { int len = strlen(val); char copy[len+1]; safencpy(copy, sizeof(copy), val, len); char *words[PATH_LEN]; chopByChar(copy, '|', words, ArraySize(words)); int k; // printTabularHeaderRow strips off (but still prints!) a trailing '|' // because of the regex, so enforce that here too so the rows after // the header don't get all out of whack for (k = 0; k < headerCount; k++) - printf("<TD class='withThinBorder'>%s</TD>", words[k]); + printf("<TD class='withThinBorder'>%s</TD>", hubEncode(tdb, words[k])); } puts("</TR>"); } } -static void vcfInfoDetails(struct vcfRecord *rec, char *trackName, int recordCount) +static void printInfoDatum(struct trackDb *tdb, const union vcfDatum datum, + const enum vcfInfoType type) +/* Print one INFO value. Same as vcfPrintDatum, except that the string forms are escaped for + * a hub track, where the VCF file was written by a stranger. */ +{ +if (type == vcfInfoString || type == vcfInfoFlag) + { + char *val = hubEncode(tdb, datum.datString); + if (startsWith("http", datum.datString)) + printf("<a target=_blank href='%s'>%s</a>", val, val); + else + printf("%s", val); + } +else + vcfPrintDatum(stdout, datum, type); +} + +static void vcfInfoDetails(struct vcfRecord *rec, struct trackDb *tdb, int recordCount) /* Expand info keys to descriptions, then print out keys and values. */ { +char *trackName = tdb->track; if (rec->infoCount == 0) return; struct vcfFile *vcff = rec->file; puts("<table>"); // wrapper table for collapsible section char infoId[32]; safef(infoId, sizeof(infoId), "infoFields%d", recordCount); jsBeginCollapsibleSectionFontSize(cart, trackName, infoId, "INFO column annotations:", FALSE, "medium"); puts("<TABLE class=\"stdTbl\">\n"); int i; for (i = 0; i < rec->infoCount; i++) { struct vcfInfoElement *el = &(rec->infoElements[i]); const struct vcfInfoDef *def = vcfInfoDefForKey(vcff, el->key); + // the INFO key, its description and its values all come from the VCF file, which for a + // hub is a stranger's file printf("<TR valign='top'><TD align=\"right\"><B>%s:</B></TD><TD>", - el->key); + hubEncode(tdb, el->key)); int j; enum vcfInfoType type = def ? def->type : vcfInfoString; if (type == vcfInfoFlag && el->count == 0) printf("Yes"); // no values, so we can't call vcfPrintDatum... // However, if this is older VCF, type vcfInfoFlag might have a value. if (looksTabular(def, el)) { // Make a special display below printf("<em>see below</em>"); } else { for (j = 0; j < el->count; j++) { if (j > 0) printf(", "); if (el->missingData[j]) printf("."); else - vcfPrintDatum(stdout, el->values[j], type); + printInfoDatum(tdb, el->values[j], type); } } if (def != NULL && !looksTabular(def, el)) - printf(" </TD><TD>%s", def->description); + printf(" </TD><TD>%s", hubEncode(tdb, def->description)); else printf("</TD><TD>"); printf("</TD></TR>\n"); } puts("</TABLE>"); jsEndCollapsibleSection(); puts("</table>"); // close the wrapper around the collapsible section // Now show the tabular fields, if any for (i = 0; i < rec->infoCount; i++) { struct vcfInfoElement *el = &(rec->infoElements[i]); const struct vcfInfoDef *def = vcfInfoDefForKey(vcff, el->key); if (looksTabular(def, el)) { puts("<BR>"); - printf("<B>%s</B>: %s<BR>\n", el->key, def->description); + printf("<B>%s</B>: %s<BR>\n", hubEncode(tdb, el->key), hubEncode(tdb, def->description)); puts("<TABLE class='stdTbl'>"); - int headerCount = printTabularHeaderRow(def); - printTabularData(el, headerCount); + int headerCount = printTabularHeaderRow(tdb, def); + printTabularData(tdb, el, headerCount); puts("</TABLE>"); } } } struct sampleMeta /* Metadata columns for one sample, loaded from sampleMetadataFile. */ { char **values; /* Array of column values */ }; static void loadSampleMetadata(struct trackDb *tdb, struct hash **retHash, char ***retColNames, int *retColCount) /* Load sample metadata from file specified in trackDb setting sampleMetadataFile. * File format: tab-separated, first line is header starting with #sample. @@ -552,31 +573,32 @@ if (showLeftBase) dyStringPrintf(dy, "(%c)", leftBase); abbreviateLongSeq(rec->alleles[i], endLength, showLength, dy); if (encodeHtml) displayAls[i] = htmlEncode(dy->string); else displayAls[i] = cloneString(dy->string); } } static void vcfRecordDetails(struct trackDb *tdb, struct vcfRecord *rec, int recordCount) /* Display the contents of a single line of VCF, assumed to be from seqName * (using seqName instead of rec->chrom because rec->chrom might lack "chr"). */ { if (isNotEmpty(rec->name) && differentString(rec->name, ".")) - printf("<B>Name:</B> %s<BR>\n", rec->name); + // the ID column comes from the VCF file, a hub's is a stranger's file + printf("<B>Name:</B> %s<BR>\n", hubEncode(tdb, rec->name)); // Add some special URL substitution variables for ExAC/GnomAD-style links struct slPair *substFields = slPairNew("ref", rec->alleles[0]); substFields->next = slPairNew("firstAlt", rec->alleles[1]); char posString[64]; safef(posString, sizeof posString, "%d", rec->chromStart+1); substFields->next->next = slPairNew("pos", posString); char *label = rec->name; if ((isEmpty(rec->name) || sameString(rec->name, ".")) && (startsWith("exac", tdb->track) || startsWith("gnomad", tdb->track))) { struct dyString *dyLabel = dyStringCreate("%s-%s-%s-%s", skipChr(rec->chrom), posString, rec->alleles[0], rec->alleles[1]); label = dyStringCannibalize(&dyLabel); } printCustomUrlWithFields(tdb, rec->name, label, TRUE, substFields); @@ -609,31 +631,31 @@ seqName, rec->chromStart, formName); printf("</TABLE></FORM>\n"); } } char leftBase = rec->alleles[0][0]; unsigned int vcfStart = vcfRecordTrimIndelLeftBase(rec); boolean showLeftBase = (rec->chromStart == vcfStart+1); (void)vcfRecordTrimAllelesRight(rec); char *displayAls[rec->alleleCount]; makeDisplayAlleles(rec, showLeftBase, leftBase, 20, TRUE, FALSE, displayAls); printPosOnChrom(seqName, rec->chromStart, rec->chromEnd, NULL, FALSE, rec->name); printf("<B>Reference allele:</B> %s<BR>\n", displayAls[0]); vcfAltAlleleDetails(rec, displayAls); vcfQualDetails(rec); vcfFilterDetails(rec); -vcfInfoDetails(rec, tdb->track, recordCount); +vcfInfoDetails(rec, tdb, recordCount); pgSnpCodingDetail(rec); makeDisplayAlleles(rec, showLeftBase, leftBase, 5, FALSE, TRUE, displayAls); vcfGenotypesDetails(rec, tdb, displayAls); } void doVcfDetailsCore(struct trackDb *tdb, char *fileOrUrl, boolean isTabix, struct featureBits **pFbList, int rgnStart, int rgnEnd) /* Show item details using fileOrUrl. */ { if (!pFbList) genericHeader(tdb, NULL); int start; int end; if (pFbList) { start = rgnStart;