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/bigBed.c src/hg/hgTables/bigBed.c
index 202f0a9..70d433f 100644
--- src/hg/hgTables/bigBed.c
+++ src/hg/hgTables/bigBed.c
@@ -167,31 +167,31 @@
 /* Get beds a region at a time. */
 struct bed *bedList = NULL;
 struct region *region;
 for (region = regionList; region != NULL; region = region->next)
     addFilteredBedsOnRegion(bbi, region, table, filter, lm, &bedList);
 slReverse(&bedList);
 
 /* Clean up and return. */
 if (retFieldCount != NULL)
 	*retFieldCount = bbi->definedFieldCount;
 bbiFileClose(&bbi);
 freeMem(fileName);
 return bedList;
 }
 
-void bigBedTabOut(char *db, char *table, struct sqlConnection *conn, char *fields, FILE *f)
+void bigBedTabOut(char *db, char *table, struct sqlConnection *conn, char *fields, FILE *f, char outSep)
 /* Print out selected fields from Big Bed.  If fields is NULL, then print out all fields. */
 {
 if (f == NULL)
     f = stdout;
 
 /* Convert comma separated list of fields to array. */
 int fieldCount = chopByChar(fields, ',', NULL, 0);
 char **fieldArray;
 AllocArray(fieldArray, fieldCount);
 chopByChar(fields, ',', fieldArray, fieldCount);
 
 /* Get list of all fields in big bed and turn it into a hash of column indexes keyed by
  * column name. */
 struct hash *fieldHash = hashNew(0);
 struct slName *bb, *bbList = bigBedGetFields(table, conn);
@@ -201,33 +201,41 @@
 
 // If bigBed has name column, look up pasted/uploaded identifiers if any:
 struct hash *idHash = NULL;
 if (slCount(bbList) >= 4)
     idHash = identifierHash(db, table);
 
 /* Create an array of column indexes corresponding to the selected field list. */
 int *columnArray;
 AllocArray(columnArray, fieldCount);
 for (i=0; i<fieldCount; ++i)
     {
     columnArray[i] = hashIntVal(fieldHash, fieldArray[i]);
     }
 
 /* Output row of labels */
-fprintf(f, "#%s", fieldArray[0]);
+fprintf(f, "#");
+if (outSep == ',') fputc('"', f);
+fprintf(f, "%s", fieldArray[0]);
+if (outSep == ',') fputc('"', f);
 for (i=1; i<fieldCount; ++i)
-    fprintf(f, "\t%s", fieldArray[i]);
+    {
+    fputc(outSep, f);
+    if (outSep == ',') fputc('"', f);
+    fprintf(f, "%s", fieldArray[i]);
+    if (outSep == ',') fputc('"', f);
+    }
 fprintf(f, "\n");
 
 /* Open up bigBed file. */
 char *fileName = bigBedFileName(table, conn);
 struct bbiFile *bbi =  bigBedFileOpenAlias(fileName, chromAliasChromToAliasHash(database));
 struct asObject *as = bigBedAsOrDefault(bbi);
 struct asFilter *filter = NULL;
 
 if (anyFilter())
     {
     filter = asFilterFromCart(cart, db, table, as);
     if (filter)
         {
 	fprintf(f, "# Filtering on %d columns\n", slCount(filter->columnList));
 	}
@@ -251,57 +259,71 @@
     {
         names[ii] = nameList->name;
         nameList = nameList->next;
     }
 
     struct lm *lm = lmInit(0);
     struct bigBedInterval *ivList = bigBedMultiNameQuery(bbi, bpt, fieldIx, names, count, lm);
     char chromBuf[4096];
     struct bigBedInterval *interval, *prevInterval = NULL;
     for (interval = ivList; interval != NULL; prevInterval = interval, interval = interval->next)
         {       
         bigBedIntervalToRowLookupChrom(interval, prevInterval, bbi, chromBuf, sizeof chromBuf, startBuf, endBuf, row, bbi->fieldCount);
         if (asFilterOnRow(filter, row))
             {
             int i;
+            if (outSep == ',') fputc('"', f);
             fprintf(f, "%s", row[columnArray[0]]);
+            if (outSep == ',') fputc('"', f);
             for (i=1; i<fieldCount; ++i)
-                fprintf(f, "\t%s", row[columnArray[i]]);
+                {
+                fputc(outSep, f);
+                if (outSep == ',') fputc('"', f);
+                fprintf(f, "%s", row[columnArray[i]]);
+                if (outSep == ',') fputc('"', f);
+                }
             fprintf(f, "\n");
             }
         }
     }
 else 
     {
     /* Loop through outputting each region */
     struct region *region, *regionList = getRegions();
     for (region = regionList; region != NULL; region = region->next)
         {
         struct lm *lm = lmInit(0);
         struct bigBedInterval *iv, *ivList = bigBedIntervalQuery(bbi, region->chrom,
             region->start, region->end, 0, lm);
         for (iv = ivList; iv != NULL; iv = iv->next)
             {
             bigBedIntervalToRow(iv, region->chrom, startBuf, endBuf, row, bbi->fieldCount);
             if (asFilterOnRow(filter, row))
                 {
                 if ((idHash != NULL) && (hashLookup(idHash, row[3]) == NULL))
                     continue;
                 int i;
+                if (outSep == ',') fputc('"', f);
                 fprintf(f, "%s", row[columnArray[0]]);
+                if (outSep == ',') fputc('"', f);
                 for (i=1; i<fieldCount; ++i)
-                    fprintf(f, "\t%s", row[columnArray[i]]);
+                    {
+                    fputc(outSep, f);
+                    if (outSep == ',') fputc('"', f);
+                    fprintf(f, "%s", row[columnArray[i]]);
+                    if (outSep == ',') fputc('"', f);
+                    }
                 fprintf(f, "\n");
                 }
             }
         lmCleanup(&lm);
         }
     }
 
 /* Clean up and exit. */
 bbiFileClose(&bbi);
 hashFree(&fieldHash);
 freeMem(fieldArray);
 freeMem(columnArray);
 }
 
 static unsigned slCountAtMost(const void *list, unsigned max)