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 "n/a"
static void printKeysWithDescriptions(struct vcfFile *vcff, int wordCount, char **words,
struct vcfInfoDef *infoDefs, boolean stripToSymbol)
@@ -90,148 +91,168 @@
printf("Filter: "NA"
\n");
else if (rec->filterCount == 1 && sameString(rec->filters[0], "PASS"))
printf("Filter: PASS
\n");
else
{
printf("Filter failures: ");
printf("\n");
struct vcfFile *vcff = rec->file;
printKeysWithDescriptions(vcff, rec->filterCount, rec->filters, vcff->filterDefs, FALSE);
printf("\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("
");
// 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("| %s | ", words[i]);
+ printf("%s | ", hubEncode(tdb, words[i]));
puts("
");
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("");
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("| %s | ", words[k]);
+ printf("%s | ", hubEncode(tdb, words[k]));
}
puts("
");
}
}
-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("%s", 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(""); // 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("\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("| %s: | ",
- 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("see below");
}
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(" | %s", def->description);
+ printf(" | %s", hubEncode(tdb, def->description));
else
printf(" | ");
printf(" |
\n");
}
puts("
");
jsEndCollapsibleSection();
puts("
"); // 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("
");
- printf("%s: %s
\n", el->key, def->description);
+ printf("%s: %s
\n", hubEncode(tdb, el->key), hubEncode(tdb, def->description));
puts("");
- int headerCount = printTabularHeaderRow(def);
- printTabularData(el, headerCount);
+ int headerCount = printTabularHeaderRow(tdb, def);
+ printTabularData(tdb, el, headerCount);
puts("
");
}
}
}
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("Name: %s
\n", rec->name);
+ // the ID column comes from the VCF file, a hub's is a stranger's file
+ printf("Name: %s
\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("\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("Reference allele: %s
\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;