5e8e0ed53c05f2ef9b51ce897440bae308367f47 chmalee Wed Feb 9 14:45:54 2022 -0800 Add csv output option to table browser to support opening files in Excel, refs #27623. Also change the output filename message to hint that Excel can auto-recognize .csv extensions and behave appropriately. Lastly, fix output dropdown selection to properly warn on update when using GTF output option or a snp table. diff --git src/hg/hgTables/joining.c src/hg/hgTables/joining.c index 97b134b..01c7c85 100644 --- src/hg/hgTables/joining.c +++ src/hg/hgTables/joining.c @@ -34,79 +34,93 @@ struct joinedTables /* Database query result table that is joinable. */ { struct joinedTables *next; struct lm *lm; /* Local memory structure - used for row allocations. */ struct joinerDtf *fieldList; /* Fields user has requested. */ int fieldCount; /* Number of fields - calculated at start of load. */ struct joinerDtf *keyList; /* List of keys. */ int keyCount; /* Number of keys- calculated at start of load. */ struct joinedRow *rowList; /* Rows - allocated in lm. */ int rowCount; /* Number of rows. */ int maxRowCount; /* Max row count allowed (0 means no limit) */ struct dyString *filter; /* Filter if any applied. */ }; -static void joinedTablesTabOutFile(struct joinedTables *joined, FILE *f) -/* write out fields to file handle */ +static void joinedTablesSepOutFile(struct joinedTables *joined, FILE *f, char outSep) +/* write outSep separated fields to file handle */ { struct joinedRow *jr; struct joinerDtf *field; int outCount = 0; if (f == NULL) f = stdout; /* Print out field names. */ if (joined->filter) { fprintf(f, "#filter: %s\n", joined->filter->string); } fprintf(f, "#"); for (field = joined->fieldList; field != NULL; field = field->next) { + if (outSep == ',') fputc('"', f); fprintf(f, "%s.%s.%s", field->database, field->table, field->field); + if (outSep == ',') fputc('"', f); if (field->next == NULL) fprintf(f, "\n"); else - fprintf(f, "\t"); + fprintf(f, "%c", outSep); } for (jr = joined->rowList; jr != NULL; jr = jr->next) { int i; if (jr->passedFilter) { for (i=0; i<joined->fieldCount; ++i) { struct slName *s; if (i != 0) - fprintf(f, "\t"); + fprintf(f, "%c", outSep); s = jr->fields[i]; if (s == NULL) + { + if (outSep == ',') fputc('"', f); fprintf(f, "n/a"); + if (outSep == ',') fputc('"', f); + } else if (s->next == NULL) + { + if (outSep == ',') fputc('"', f); fprintf(f, "%s", s->name); + if (outSep == ',') fputc('"', f); + } else { char *lastS = NULL; + if (outSep == ',') fputc('"', f); while (s != NULL) { if (lastS == NULL || !sameString(lastS, s->name)) + { fprintf(f, "%s,", s->name); + } lastS = s->name; s = s->next; } + if (outSep == ',') fputc('"', f); } } fprintf(f, "\n"); ++outCount; } } } static struct joinedTables *joinedTablesNew(int fieldCount, int keyCount, int maxRowCount) /* Make up new empty joinedTables. */ { struct joinedTables *jt; AllocVar(jt); @@ -979,35 +993,36 @@ ret = (! allSameTable(dtfList, filterTables)); if (pDtfList != NULL) *pDtfList = dtfList; else joinerDtfFreeList(&dtfList); if (pFilterTables != NULL) *pFilterTables = filterTables; else joinerDtfFreeList(&filterTables); return ret; } -void tabOutSelectedFields( +void sepOutSelectedFields( char *primaryDb, /* The primary database. */ char *primaryTable, /* The primary table. */ FILE *f, /* file for output, null for stdout */ - struct slName *fieldList) /* List of db.table.field */ + struct slName *fieldList, /* List of db.table.field */ + char outSep) /* The separator for the output */ /* Do tab-separated output on selected fields, which may * or may not include multiple tables. */ { struct joinerDtf *dtfList = NULL; struct joinerDtf *filterTables = NULL; boolean doJoin = joinRequired(primaryDb, primaryTable, fieldList, &dtfList, &filterTables); boolean hasIdentifiers = (identifierFileName() != NULL); char *regionType = cartUsualString(cart, hgtaRegionType, "genome"); boolean hasRegions = sameString(regionType, hgtaRegionTypeRange) || sameString(regionType, hgtaRegionTypeEncode) || (sameString(regionType, hgtaRegionTypeUserRegions) && (userRegionsFileName() != NULL)); @@ -1065,39 +1080,39 @@ if (isBigBed(database, dtfList->table, NULL, ctLookupName)) makeBigBedOrderedCommaFieldList(dtfList, dy); else if (isLongTabixTable(dtfList->table)) makeLongTabixOrderedCommaFieldList(dtfList, dy); else if (isBamTable(dtfList->table)) makeBamOrderedCommaFieldList(dtfList, dy); else if (isVcfTable(dtfList->table, NULL)) makeVcfOrderedCommaFieldList(dtfList, dy); else if (isHicTable(dtfList->table)) makeHicOrderedCommaFieldList(dtfList, dy); else if (isCustomTrack(dtfList->table)) makeCtOrderedCommaFieldList(dtfList, dy); else makeDbOrderedCommaFieldList(conn, dtfList->table, dtfList, dy); - doTabOutTable(dtfList->database, dtfList->table, f, conn, dy->string); + doTabOutTable(dtfList->database, dtfList->table, f, conn, dy->string, outSep); hFreeConn(&conn); } else { struct joiner *joiner = allJoiner; struct joinedTables *joined = joinedTablesCreate(joiner, primaryDb, primaryTable, dtfList, filterTables, 1000000, getRegions()); - joinedTablesTabOutFile(joined, f); + joinedTablesSepOutFile(joined, f, outSep); joinedTablesFree(&joined); } joinerDtfFreeList(&dtfList); joinerDtfFreeList(&filterTables); } static struct slName *getBedFieldSlNameList(struct hTableInfo *hti, char *db, char *table) /* Return the bed-compat field list for the given table, as * slName list of "$db.$table.$field". */ { struct slName *snList = NULL, *sn = NULL; int fieldCount = 0; char *fields = NULL;