06620d160679a0d2e77dedbe35b35edeea08c517
chmalee
  Mon Jun 3 16:17:48 2019 -0700
Add checks to hubCheck and hgTrackUi for incorrect tag=val pairs in a subgroup line, refs #13428

diff --git src/hg/lib/hui.c src/hg/lib/hui.c
index 585e876..c0eb9f1 100644
--- src/hg/lib/hui.c
+++ src/hg/lib/hui.c
@@ -2506,37 +2506,30 @@
 
 
 static char *tagEncode(char *name)
 // Turns out css classes cannot begin with a number.  So prepend 'A'
 // If this were more widely used, could move to cheapcgi.c.
 {
 if (!isdigit(*name))
     return name;
 
 char *newName = needMem(strlen(name)+2);
 *newName = 'A';
 strcpy(newName+1,name);
 return newName;
 }
 
-typedef struct _dimensions
-    {
-    int count;
-    char**names;
-    char**subgroups;
-    char* setting;
-    } dimensions_t;
 
 boolean dimensionsExist(struct trackDb *parentTdb)
 // Does this parent track contain dimensions?
 {
 return (trackDbSetting(parentTdb, "dimensions") != NULL);
 }
 
 static dimensions_t *dimensionSettingsGet(struct trackDb *parentTdb)
 // Parses any dimemnions setting in parent of subtracks
 {
 dimensions_t *dimensions = needMem(sizeof(dimensions_t));
 dimensions->setting = cloneString(trackDbSetting(parentTdb, "dimensions"));  // To be freed later
 if (dimensions->setting == NULL)
     {
     freeMem(dimensions);
@@ -2660,30 +2653,34 @@
     return NULL;
     }
 members->groupTag   = words[0];
 members->groupTitle = strSwapChar(words[1],'_',' '); // Titles replace '_' with space
 members->tags       = needMem(count*sizeof(char*));
 members->titles     = needMem(count*sizeof(char*));
 for (ix = 2,members->count=0; ix < count; ix++)
     {
     char *name,*value;
     if (parseAssignment(words[ix], &name, &value))
 	{
 	members->tags[members->count]  = tagEncode(name);
 	members->titles[members->count] = strSwapChar(value,'_',' ');
 	members->count++;
 	}
+    else
+        {
+        warn("Subgroup \"%s\" is missing a tag=val pair", words[1]);
+        }
     }
 tdbExtrasMembersSet(parentTdb, groupNameOrTag, members);
 
 return members;
 }
 
 static int membersSubGroupIx(members_t* members, char *tag)
 // Returns the index of the subgroup within the members struct (or -1)
 {
 int ix = 0;
 for (ix=0;ix<members->count;ix++)
     {
     if (members->tags[ix] != NULL && sameString(members->tags[ix],tag))
 	return ix;
     }
@@ -2777,45 +2774,30 @@
     //subgroupMembersFree(&members);   // don't bother freeing
     return NULL;
     }
 
 return members;
 }
 
 enum
     {
     dimV=0, // View first
     dimX=1, // X & Y next
     dimY=2,
     dimA=3, // dimA is start of first of the optional non-matrix, non-view dimensions
     };
 
-typedef struct _membersForAll
-    {
-    int abcCount;
-    int dimMax;               // Arrays of "members" structs will be ordered as
-			      //    [view][dimX][dimY][dimA]... with first 3 in fixed spots
-			      //    and rest as found (and non-empty)
-    boolean filters;          // ABCs use filterComp boxes (as upposed to check boxes
-    dimensions_t *dimensions; // One struct describing "deimensions" setting"
-			      //    (e.g. dimX:cell dimY:antibody dimA:treatment)
-    members_t* members[27];   // One struct for each dimension describing groups in dimension
-			      //    (e.g. cell: GM12878,K562)
-    char* checkedTags[27];  // FIXME: Should move checkedTags into
-			    // membersForAll->members[ix]->selected;
-    char letters[27];
-    } membersForAll_t;
 
 
 static char* abcMembersChecked(struct trackDb *parentTdb, struct cart *cart, members_t* members,
 			   char letter)
 // returns a string of subGroup tags which are currently checked
 {
 char settingName[SMALLBUF];
 int mIx;
 if (members->selected == NULL)
     members->selected = needMem(members->count * sizeof(boolean));
 safef(settingName, sizeof(settingName), "%s.filterComp.%s",parentTdb->track,members->groupTag);
 struct slName *options = cartOptionalSlNameList(cart,settingName);
 if (options != NULL)
     {
     if (sameWord(options->name,"All")) // filterComp returns "All" meaning every option selected
@@ -2900,31 +2882,31 @@
 	if (ixOut < ixIn)  // Collapse if necessary
 	    { // NOTE: Don't I wish I had made these as an slList ages ago! (tim)
 	    membersForAll->members[ixOut]     = membersForAll->members[ixIn];
 	    membersForAll->checkedTags[ixOut] = membersForAll->checkedTags[ixIn];
 	    membersForAll->letters[ixOut]     = membersForAll->letters[ixIn];
 	    }
 	ixOut++;
 	}
     }
 membersForAll->dimMax   = ixOut;
 membersForAll->abcCount = membersForAll->dimMax - dimA;
 
 return membersForAll;
 }
 
-static membersForAll_t* membersForAllSubGroupsGet(struct trackDb *parentTdb, struct cart *cart)
+membersForAll_t* membersForAllSubGroupsGet(struct trackDb *parentTdb, struct cart *cart)
 // Returns all the parents subGroups and members
 {
 membersForAll_t *membersForAll = tdbExtrasMembersForAll(parentTdb);
 if (membersForAll != NULL)
     return membersForAll;  // Already retrieved, so don't do it again
 
 int ix;
 membersForAll = needMem(sizeof(membersForAll_t));
 if (tdbIsCompositeView(parentTdb->subtracks))  // view must have viewInMidle tdb in tree
     membersForAll->members[dimV]=subgroupMembersGet(parentTdb,"view");
 membersForAll->letters[dimV]='V';
 membersForAll->dimMax=dimA;  // This can expand, depending upon ABC dimensions
 membersForAll->dimensions = dimensionSettingsGet(parentTdb);
 if (membersForAll->dimensions != NULL)
     {