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;