800312e66545e7d7fd4805c99717bd32c4b47c33
chmalee
  Tue Feb 18 16:49:45 2020 -0800
Fixing spurious newline stripping in hubCheck. When checking a subGroupN line for a composite track, a warn is thrown and hubCheck continues, if a single stanza has multiple cases of bad subGroups, then all these warning messages were stuck together and not being printed correctly, refs #24958

diff --git src/hg/utils/hubCheck/hubCheck.c src/hg/utils/hubCheck/hubCheck.c
index 424c8d8..73070d2 100644
--- src/hg/utils/hubCheck/hubCheck.c
+++ src/hg/utils/hubCheck/hubCheck.c
@@ -465,32 +465,32 @@
 struct dyString *folderString = dyStringNew(0);
 dyStringPrintf(folderString, "{icon: '../../images/folderC.png', id: '%s', "
     "text:\"%s\", parent:'%s',"
     "li_attr:{title:'%s'}, children:%s, state: {opened: %s}}",
     id, text, parent, title, children ? "true" : "false", openFolder ? "true" : "false");
 return dyStringCannibalize(&folderString);
 }
 
 char *makeChildObjectString(char *id, char *title, char *shortLabel, char *longLabel,
     char *color, char *name, char *text, char *parent)
 /* Construct a single child item for one of the jstree arrays */
 {
 struct dyString *item = dyStringNew(0);
 dyStringPrintf(item, "{icon: 'fa fa-plus', id:'%s', li_attr:{class: 'hubError', title: '%s', "
         "shortLabel: '%s', longLabel: '%s', color: '%s', name:'%s'}, "
-        "text:\"%s\", parent: '%s', state: {opened: true}}",
-        id, title, shortLabel, longLabel, color, name, text, parent);
+        "text:'%s', parent: '%s', state: {opened: true}}",
+        id, title, shortLabel, longLabel, color, name, replaceChars(text, "'", "\\'"), parent);
 return dyStringCannibalize(&item);
 }
 
 void hubErr(struct dyString *errors, char *message, struct trackHub *hub)
 /* Construct the right javascript for the jstree for a top level hub.txt error. */
 {
 char *sl;
 char *strippedMessage = NULL;
 static int count = 0; // force a unique id for the jstree object
 char id[512];
 //TODO: Choose better default labels
 if (hub && hub->shortLabel != NULL)
     {
     sl = hub->shortLabel;
     }
@@ -528,46 +528,61 @@
 dyStringPrintf(errors, "trackData['%s'] = [%s", genomeName,
     makeChildObjectString(id, "Error Getting TrackDb", genomeName, genomeName, "#550073", genomeName, strippedMessage, genomeName));
 count++;
 }
 
 void trackDbErr(struct dyString *errors, char *message, struct trackHubGenome *genome, struct trackDb *tdb, boolean doHtml)
 /* Adds the right object for a jstree object of trackDb configuration errors.  */
 {
 if (!doHtml)
     {
     dyStringPrintf(errors, "%s", message);
     }
 else
     {
     char *strippedMessage = NULL;
+    char *splitMessages[16]; // SUBGROUP_MAX=9 but add a few extra just in case
     char parentOrTrackString[512];
     char id[512];
     static int count = 0; // forces unique ID's which the jstree object needs
+    int numMessages = 0;
+    int i = 0;
 
+    // if a subtrack is missing multiple subgroups, then message will contain
+    // at least two newline separated errors, both should be printed separately:
     if (message)
+        {
         strippedMessage = cloneString(message);
+        while (lastChar(strippedMessage) == '\n')
+            trimLastChar(strippedMessage);
+        numMessages = chopByChar(strippedMessage, '\n', splitMessages, sizeof(splitMessages));
+        }
 
-    stripChar(strippedMessage, '\n');
+    for (; i < numMessages; i++)
+        {
+        if (isNotEmpty(splitMessages[i]))
+            {
             safef(id, sizeof(id), "%s%d", trackHubSkipHubName(tdb->track), count);
             safef(parentOrTrackString, sizeof(parentOrTrackString), "%s_%s", trackHubSkipHubName(genome->name), trackHubSkipHubName(tdb->track));
             dyStringPrintf(errors, "%s,",
                     makeChildObjectString(id, "TrackDb Error", tdb->shortLabel, tdb->longLabel,
-            "#550073", trackHubSkipHubName(tdb->track), strippedMessage, parentOrTrackString));
+                    "#550073", trackHubSkipHubName(tdb->track), splitMessages[i], parentOrTrackString));
             count++;
             }
         }
+    }
+}
 
 boolean checkEmptyMembersForAll(membersForAll_t *membersForAll, struct trackDb *parentTdb)
 /* membersForAll may be allocated and exist but not have any actual members defined. */
 {
 int i;
 for (i = 0; i < ArraySize(membersForAll->members); i++)
     {
     if (membersForAll->members[i] != NULL)
         return TRUE;
     }
 return FALSE;
 }
 
 int hubCheckSubtrackSettings(struct trackHubGenome *genome, struct trackDb *tdb, struct dyString *errors, struct trackHubCheckOptions *options)
 /* Check that 'subgroups' are consistent with what is defined at the parent level */
@@ -578,34 +593,31 @@
 
 int i;
 char *subtrackName = trackHubSkipHubName(tdb->track);
 sortOrder_t *sortOrder = NULL;
 membership_t *membership = NULL;
 membersForAll_t *membersForAll = NULL;
 struct errCatch *errCatch = errCatchNew();
 
 if (errCatchStart(errCatch))
     {
     membersForAll = membersForAllSubGroupsGet(tdb->parent, NULL);
 
     // membersForAllSubGroupsGet() warns about the parent stanza, turn it into an errAbort
     if (errCatch->gotWarning)
         {
-        char *temp = cloneString(errCatch->message->string);
-        stripChar(temp, '\n');
-        dyStringClear(errCatch->message);
-        errAbort("%s", temp);
+        errAbort("%s", errCatch->message->string);
         }
 
     if (membersForAll && checkEmptyMembersForAll(membersForAll, tdb->parent))
         {
         membership = subgroupMembershipGet(tdb);
         sortOrder = sortOrderGet(NULL, tdb->parent);
 
         if (membership == NULL)
             {
             errAbort("missing 'subgroups' setting for subtrack %s", subtrackName);
             }
 
         // if a sortOrder is defined, make sure every subtrack has that membership
         if (sortOrder)
             {