b4538b995c8b87f8663861aa9cde4b8c68e83eb1
braney
  Tue Apr 7 12:52:56 2026 -0700
Fix Get DNA position text box being ignored when table is specified, and accept single-base positions. refs #10316, #15336, #37325

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

diff --git src/hg/hgc/hgc.c src/hg/hgc/hgc.c
index 4d2d8457048..84614360b20 100644
--- src/hg/hgc/hgc.c
+++ src/hg/hgc/hgc.c
@@ -5533,49 +5533,66 @@
 {
 struct trackDb *tdbList = hTrackDb(database);
 struct trackDb *ctdbList = tdbForCustomTracks();
 struct trackDb *utdbList = tdbForUserPsl();
 
 struct grp *pGrpList = NULL;
 struct trackDb *hubList = hubCollectTracks(database, &pGrpList);
 
 ctdbList = slCat(ctdbList, tdbList);
 ctdbList = slCat(ctdbList, hubList);
 tdbList = slCat(utdbList, ctdbList);
 return tdbList;
 }
 
 
+static boolean parseDnaPos(char *db, char *pos, char **retChrom, int *retStart, int *retEnd)
+/* Parse a position string that may be chrom:start-end or just chrom:pos (single base). */
+{
+if (hgParseChromRange(db, pos, retChrom, retStart, retEnd))
+    return TRUE;
+/* check for single position like chrX:67774500 (colon but no dash) */
+char *colon = strchr(pos, ':');
+if (colon != NULL && strchr(colon, '-') == NULL)
+    {
+    struct dyString *dy = dyStringCreate("%s-%s", pos, colon + 1);
+    boolean result = hgParseChromRange(db, dy->string, retChrom, retStart, retEnd);
+    dyStringFree(&dy);
+    return result;
+    }
+return FALSE;
+}
+
 void doGetDnaExtended1()
 /* Do extended case/color get DNA options. */
 {
 boolean revComp  = cartUsualBoolean(cart, "hgSeq.revComp", FALSE);
 boolean maskRep  = cartUsualBoolean(cart, "hgSeq.maskRepeats", FALSE);
 int padding5     = cartUsualInt(cart, "hgSeq.padding5", 0);
 int padding3     = cartUsualInt(cart, "hgSeq.padding3", 0);
 int lineWidth    = cartUsualInt(cart, "lineWidth", 60);
 char *casing     = cartUsualString(cart, "hgSeq.casing", "");
 char *repMasking = cartUsualString(cart, "hgSeq.repMasking", "");
 boolean caseUpper= FALSE;
 char *pos = NULL;
 
 struct trackDb *tdbList = loadTracks();
 
 cartWebStart(cart, database, "Extended DNA Case/Color");
 
 if (NULL != (pos = stripCommas(cartOptionalString(cart, "getDnaPos"))))
-    hgParseChromRange(database, pos, &seqName, &winStart, &winEnd);
+    parseDnaPos(database, pos, &seqName, &winStart, &winEnd);
 if (winEnd - winStart > 5000000)
     {
     printf("Please zoom in to 5 million bases or less to color the DNA");
     return;
     }
 
 printf("<H1>Extended DNA Case/Color Options</H1>\n");
 puts(
      "Use this page to highlight features in genomic DNA text. "
      "DNA covered by a particular track can be highlighted by "
      "case, underline, bold, italic, or color.  See below for "
      "details about color, and for examples. <B>Tracks in "
      "&quot;hide&quot; display mode are not shown in the grid below.</B> <P>");
 
 if (cgiBooleanDefined("hgSeq.maskRepeats"))
@@ -5833,54 +5850,58 @@
 if (sameString(action, EXTENDED_DNA_BUTTON))
     {
     doGetDnaExtended1();
     return;
     }
 // This output probably should be just text/plain but
 // trying to support the fancy warn handler box requires html.
 // But we want to keep it very simple and close to a plain text dump.
 
 cartHtmlStart("DNA");
 puts("<PRE>");
 if (tbl[0] == 0)
     {
     itemCount = 1;
     if ( NULL != (pos = stripCommas(cartOptionalString(cart, "getDnaPos"))) &&
-         hgParseChromRange((dbIsFound ? database : NULL), pos, &chrom, &start, &end))
+         parseDnaPos((dbIsFound ? database : NULL), pos, &chrom, &start, &end))
         {
         hgSeqRange(database, chrom, start, end, '?', "dna");
         }
     else
         {
         hgSeqRange(database, seqName, cartInt(cart, "l"), cartInt(cart, "r"),
                    '?', "dna");
         }
     }
 else
     {
     struct hTableInfo *hti = NULL;
     char rootName[HDB_MAX_TABLE_STRING];
     char parsedChrom[HDB_MAX_CHROM_STRING];
 
     /* use the values from the dnaPos dialog box */
     if (!( NULL != (pos = stripCommas(cartOptionalString(cart, "getDnaPos"))) &&
-         hgParseChromRange(database, pos, &chrom, &start, &end)))
+         parseDnaPos(database, pos, &chrom, &start, &end)))
 	 {
 	 /* if can't get DnaPos from dialog box, use "o" and "t" */
 	 start = cartInt(cart, "o");
 	 end = cartInt(cart, "t");
 	 }
+    else
+	 {
+	 seqName = chrom;
+	 }
 
     /* Table might be a custom track if it's not in the database,
      * or bigBed if it is in the database but has only one column called 'fileName';
      * in which case, just get DNA as if no table were given. */
     hParseTableName(database, tbl, rootName, parsedChrom);
     if (!trackHubDatabase(database))
 	hti = hFindTableInfo(database, seqName, rootName);
     if (hti == NULL || hti->startField[0] == 0)
 	{
 	itemCount = 1;
 	hgSeqRange(database, seqName, start, end, '?', tbl);
 	}
     else
 	{
 	char *where = NULL;
@@ -6189,31 +6210,31 @@
 int i;
 boolean isRc = cartUsualBoolean(cart, "hgSeq.revComp", FALSE);
 boolean defaultUpper = sameString(cartString(cart, "hgSeq.casing"), "upper");
 int winSize;
 int lineWidth = cartInt(cart, "lineWidth");
 struct rgbColor *colors;
 
 struct trackDb *tdbList = loadTracks();
 
 char *pos = NULL;
 Bits *uBits;	/* Underline bits. */
 Bits *iBits;    /* Italic bits. */
 Bits *bBits;    /* Bold bits. */
 
 if (NULL != (pos = stripCommas(cartOptionalString(cart, "getDnaPos"))))
-    hgParseChromRange(database, pos, &seqName, &winStart, &winEnd);
+    parseDnaPos(database, pos, &seqName, &winStart, &winEnd);
 
 winSize = winEnd - winStart;
 uBits = bitAlloc(winSize);	/* Underline bits. */
 iBits = bitAlloc(winSize);	/* Italic bits. */
 bBits = bitAlloc(winSize);	/* Bold bits. */
 
 cartWebStart(cart, database, "Extended DNA Output");
 printf("<PRE><TT>");
 printf(">%s:%d-%d %s\n", seqName, winStart+1, winEnd,
        (isRc ? "(reverse complement)" : ""));
 seq = hDnaFromSeq(database, seqName, winStart, winEnd, dnaLower);
 if (isRc)
     reverseComplement(seq->dna, seq->size);
 if (defaultUpper)
     touppers(seq->dna);