533112afe2a2005e80cdb1f82904ea65032d4302
braney
  Sat Oct 2 11:37:34 2021 -0700
split hg/lib into two separate libaries, one only used by the cgis

diff --git src/hg/cgilib/search.c src/hg/cgilib/search.c
new file mode 100644
index 0000000..88dba26
--- /dev/null
+++ src/hg/cgilib/search.c
@@ -0,0 +1,356 @@
+// Search code which is shared between different CGIs: hgFileSearch and hgTracks(Track Search)
+
+/* Copyright (C) 2014 The Regents of the University of California 
+ * See README in this or parent directory for licensing information. */
+
+#include "search.h"
+#include "cheapcgi.h"
+#include "hdb.h"
+#include "hgConfig.h"
+#include "hPrint.h"
+#include "trix.h"
+#include "mdb.h"
+#include "subText.h"
+#include "jsHelper.h"
+
+void getSearchTrixFile(char *database, char *buf, int len)
+// Fill-in the name of the track search trix file
+{
+char *trixPath = cfgOptionDefault("browser.trixPath", "/gbdb/$db/trackDb.ix");
+struct subText *subList = subTextNew("$db", database);
+subTextStatic(subList, trixPath, buf, len);
+char *subBuf = hReplaceGbdb(buf);
+safecpy(buf, len, subBuf);
+freez(&subBuf);
+}
+
+boolean isSearchTracksSupported(char *database, struct cart *cart)
+// Return TRUE if searchTracks is supported for this database and javascript is supported too
+{
+char trixFile[HDB_MAX_PATH_STRING];
+getSearchTrixFile(database, trixFile, sizeof(trixFile));
+char *trixPath = hReplaceGbdb(trixFile);
+bool ret = udcExists(trixPath);
+freez(&trixPath);
+return ret;
+}
+
+struct slPair *fileFormatSearchWhiteList()
+// Gets the whitelist of approved file formats that is allowed for search
+{
+char *crudeTypes[] =
+    {
+    "bam",  //    "bam.bai" is now alway selected with bam,
+    "tagAlign",
+    "bed.gz",
+    "bigBed",
+    "broadPeak",
+    "narrowPeak",
+    "fastq",
+    "bigWig",
+    "wig"    // TODO: Add "other" category. TODO: make into multi-select
+    };
+char *nicerTypes[] =
+    {
+    "Alignment binary (bam) - binary SAM",  //    "Alignment binary index (bai) - binary SAM index",
+    "Alignment tags (tagAlign)",
+    "bed - browser extensible data",
+    "bigBed - self index, often remote bed format",
+    "Peaks Broad (broadPeak) - ENCODE large region peak format",
+    "Peaks Narrow (narrowPeak) - ENCODE small region peak format",
+    "Raw Sequence (fastq) - High throughput sequence format",
+    "Signal (bigWig) - self index, often remote wiggle format",
+    "Signal (wig) - wiggle format"
+    };
+
+struct slPair *fileTypes = NULL;
+int ix = 0, count = sizeof(crudeTypes)/sizeof(char *);
+for (ix=0;ix<count;ix++)
+    slPairAdd(&fileTypes, crudeTypes[ix],cloneString(nicerTypes[ix]));
+return fileTypes;
+}
+
+char *fileFormatSelectHtml(char *name, char *selected, char *event, char *javascript, char *style)
+// returns an allocated string of HTML for the fileType select drop down
+{
+struct slPair *fileTypes = fileFormatSearchWhiteList();
+if (slCount(fileTypes) > 0)
+    {
+    char *dropDownHtml = cgiMakeSingleSelectDropList(name,fileTypes,selected,
+                                                     ANYLABEL, NULL, event, javascript, style, NULL);
+    slPairFreeList(&fileTypes);
+    return dropDownHtml;
+    }
+return NULL;
+}
+
+struct slPair *mdbSelectPairs(struct cart *cart, struct slPair *mdbVars)
+// Returns the current mdb  vars and vals in the table of drop down selects
+{
+// figure out how many metadata selects are visible.
+int numMetadataSelects = 0;
+
+struct slPair *mdbSelectPairs = NULL;
+if (mdbVars == NULL)
+    return 0;
+
+// Get the current number of rows in the table of mdb selects
+for (;;)
+    {
+    char buf[256];
+    safef(buf, sizeof(buf), "%s%d", METADATA_NAME_PREFIX, numMetadataSelects + 1);
+    char *str = cartOptionalString(cart, buf);
+    if (isEmpty(str))
+        break;
+    else
+        numMetadataSelects++;
+    }
+
+// Requesting to add or delete any?
+int delSearchSelect = cartUsualInt(cart, TRACK_SEARCH_DEL_ROW, 0);   // 1-based row to delete
+int addSearchSelect = cartUsualInt(cart, TRACK_SEARCH_ADD_ROW, 0);   // 1-based row to insert after
+if (delSearchSelect)
+    numMetadataSelects--;
+if (addSearchSelect)
+    numMetadataSelects++;
+
+if (numMetadataSelects)
+    {
+    int ix;
+    char buf[256];
+    for (ix = 0; ix < numMetadataSelects; ix++)
+        {
+        int offset;   // used to handle additions/deletions
+        if (addSearchSelect > 0 && ix >= addSearchSelect)
+            offset = 0; // do nothing to offset (i.e. copy data from previous row)
+        else if (delSearchSelect > 0 && ix + 1 >= delSearchSelect)
+            offset = 2;
+        else
+            offset = 1;
+        safef(buf, sizeof(buf), "%s%d", METADATA_NAME_PREFIX, ix + offset);
+        char *var = cartOptionalString(cart, buf);
+        char *val = NULL;
+
+        // We need to make sure var is valid in this assembly; if it isn't, reset it to "cell".
+        if (slPairFindVal(mdbVars,var) == NULL)
+            var = "cell";
+        else
+            {
+            safef(buf, sizeof(buf), "%s%d", METADATA_VALUE_PREFIX, ix + offset);
+            enum cvSearchable searchBy = cvSearchMethod(var);
+            if (searchBy == cvSearchByMultiSelect)
+                {
+                // Multi-selects as comma delimited list of values
+                struct slName *vals = cartOptionalSlNameList(cart,buf);
+                if (vals)
+                    {
+                    val = slNameListToString(vals,','); // A comma delimited list of values
+                    slNameFreeList(&vals);
+                    }
+                }
+            else if (searchBy == cvSearchBySingleSelect
+                 ||  searchBy == cvSearchByFreeText
+                 ||  searchBy == cvSearchByWildList)
+                val = cloneString(cartUsualString(cart, buf,ANYLABEL));
+            //else if (searchBy == cvSearchByDateRange || searchBy == cvSearchByIntegerRange)
+            //    {
+            //    // TO BE IMPLEMENTED
+            //    }
+
+            if (val != NULL && sameString(val, ANYLABEL))
+                val = NULL;
+            }
+        slPairAdd(&mdbSelectPairs,var,val); // val already cloned
+        }
+    if (delSearchSelect > 0)
+        {
+        safef(buf, sizeof(buf), "%s%d", METADATA_NAME_PREFIX, numMetadataSelects + 1);
+        cartRemove(cart, buf);
+        safef(buf, sizeof(buf), "%s%d", METADATA_VALUE_PREFIX, numMetadataSelects + 1);
+        cartRemove(cart, buf);
+        }
+    }
+else
+    {
+    // create defaults
+    slPairAdd(&mdbSelectPairs,"cell",    NULL);
+    slPairAdd(&mdbSelectPairs,"antibody",NULL);
+    }
+
+slReverse(&mdbSelectPairs);
+return mdbSelectPairs;
+}
+
+char *mdbSelectsHtmlRows(struct sqlConnection *conn,struct slPair *mdbSelects,
+                         struct slPair *mdbVars,int cols,boolean fileSearch)
+// genereates the html for the table rows containing mdb var and val selects.
+// Assume tableSearch unless fileSearch
+{
+struct dyString *output = dyStringNew(1024);
+
+dyStringPrintf(output,"<tr><td colspan='%d' align='right' class='lineOnTop' style='height:20px; "
+                      "max-height:20px;'><em style='color:%s; width:200px;'>ENCODE terms</em>"
+                      "</td></tr>\n", cols,COLOR_DARKGREY);
+
+struct slPair *mdbSelect = mdbSelects;
+int row = 0;
+for (;mdbSelect != NULL; mdbSelect = mdbSelect->next)
+    {
+    char buf[256];
+    char *dropDownHtml = NULL;
+
+    char id[256];
+    char javascript[1024];
+
+    #define PLUS_MINUS_BUTTON "<input type='button' id='%s' value='%c' " \
+                              "style='font-size:.7em;' title='%s'>"
+    #define PLUS_MINUS_BUTTON_JS "findTracks.mdbSelectPlusMinus(this,%d);"
+    #define ADD_PM_BUTTON(type,num,value) \
+	    safef(id, sizeof id, "%sButton%d", (type), (num)); \
+            dyStringPrintf(output,PLUS_MINUS_BUTTON, id, (value), \
+                           ((value) == '+' ? "add another row after":"delete")); \
+	    safef(javascript, sizeof javascript, PLUS_MINUS_BUTTON_JS, (num)); \
+	    jsOnEventById("click", id, javascript);
+	    
+    dyStringAppend(output,"<tr valign='top' class='mdbSelect'><td nowrap>\n");
+    row++;
+
+    if (slCount(mdbSelects) > 2 || row > 2)
+	{
+        ADD_PM_BUTTON("minus", row, '-')
+	}
+    else
+        dyStringAppend(output,"&nbsp;");
+    ADD_PM_BUTTON("plus", row, '+')
+
+    dyStringAppend(output,"</td><td>and&nbsp;</td><td colspan=3 nowrap>\n");
+    safef(buf, sizeof(buf), "%s%i", METADATA_NAME_PREFIX, row);
+
+    // Left side select of vars
+    dropDownHtml = cgiMakeSingleSelectDropList(buf, mdbVars,mdbSelect->name, NULL,"mdbVar",
+    	"change", "findTracks.mdbVarChanged(this);", "font-size:.9em;", NULL);
+    if (dropDownHtml)
+        {
+        dyStringAppend(output,dropDownHtml);
+        freeMem(dropDownHtml);
+        }
+
+    // Right side select of vals
+    safef(buf, sizeof(buf), "%s%i", METADATA_VALUE_PREFIX, row);
+    enum cvSearchable searchBy = cvSearchMethod(mdbSelect->name);
+    if (searchBy == cvSearchBySingleSelect || searchBy == cvSearchByMultiSelect)
+        {
+        dyStringPrintf(output,"</td>\n<td align='right' id='isLike%i' style='width:10px; "
+                              "white-space:nowrap;'>is%s</td>\n<td nowrap id='%s' "
+                              "style='max-width:600px;'>\n",
+                              row,(searchBy == cvSearchByMultiSelect?" among":""),buf);
+        struct slPair *pairs = mdbValLabelSearch(conn, mdbSelect->name, MDB_VAL_STD_TRUNCATION,
+                                                 FALSE, !fileSearch, fileSearch);
+                                                        // not tags, either a file or table search
+        if (slCount(pairs) > 0)
+            {
+            char *dropDownHtml = cgiMakeSelectDropList((searchBy == cvSearchByMultiSelect),
+                                                buf, pairs,mdbSelect->val, ANYLABEL,"mdbVal",
+                                                "change", "findTracks.mdbValChanged(this);",
+                                                "min-width:200px; font-size:.9em;", NULL);
+            if (dropDownHtml)
+                {
+                dyStringAppend(output,dropDownHtml);
+                freeMem(dropDownHtml);
+                }
+            slPairFreeList(&pairs);
+            }
+        }
+    else if (searchBy == cvSearchByFreeText)
+        {
+        dyStringPrintf(output,"</td><td align='right' id='isLike%i' style='width:10px; "
+                              "white-space:nowrap;'>contains</td>\n<td nowrap id='%s' "
+                              "style='max-width:600px;'>\n",row,buf);
+	safef(id, sizeof id, "%i_change", row);
+        dyStringPrintf(output,"<input type='text' name='%s' id='%s' value='%s' class='mdbVal freeText' "
+                              "style='max-width:310px; width:310px; font-size:.9em;'>\n",
+                              buf,id,(mdbSelect->val ? (char *)mdbSelect->val: ""));
+	jsOnEventById("change", id, "findTracks.mdbVarChanged(true);");
+        }
+    else if (searchBy == cvSearchByWildList)
+        {
+        dyStringPrintf(output,"</td><td align='right' id='isLike%i' style='width:10px; "
+                              "white-space:nowrap;'>is among</td>\n<td nowrap id='%s' "
+                              "style='max-width:600px;'>\n",row,buf);
+	safef(id, sizeof id, "%i_change", row);
+        dyStringPrintf(output,"<input type='text' name='%s' id='%s' value='%s' class='mdbVal wildList' "
+                              "title='enter comma separated list of values' "
+                              "style='max-width:310px; width:310px; font-size:.9em;'>\n",
+                              buf,id,(mdbSelect->val ? (char *)mdbSelect->val: ""));
+	jsOnEventById("change", id, "findTracks.mdbVarChanged(true);");
+        }
+    //else if (searchBy == cvSearchByDateRange || searchBy == cvSearchByIntegerRange)
+    //    {
+    //    // TO BE IMPLEMENTED
+    //    }
+    dyStringPrintf(output,"<span id='helpLink%i'>&nbsp;</span></td>\n", row);
+    dyStringPrintf(output,"</tr>\n");
+    }
+
+    dyStringPrintf(output,"<tr><td colspan='%d' align='right' style='height:10px; "
+                          "max-height:10px;'>&nbsp;</td></tr>", cols);
+
+return dyStringCannibalize(&output);
+}
+
+
+static boolean searchMatchToken(char *string, char *token)
+{
+// do this with regex ? Would require all sorts of careful parsing for ()., etc.
+if (string == NULL)
+    return (token == NULL);
+if (token == NULL)
+    return TRUE;
+
+if (!strchr(token,'*') && !strchr(token,'?'))
+    return (strcasestr(string,token) != NULL);
+
+char wordWild[1024];
+safef(wordWild,sizeof wordWild,"*%s*",token);
+return wildMatch(wordWild, string);
+}
+
+boolean searchNameMatches(struct trackDb *tdb, struct slName *wordList)
+// returns TRUE if all words in preparsed list matches short or long label
+// A "word" can be "multiple words" (parsed from quoteed string).
+{
+if (tdb->shortLabel == NULL || tdb->longLabel == NULL)
+    return (wordList != NULL);
+
+struct slName *word = wordList;
+for (; word != NULL; word = word->next)
+    {
+    if (!searchMatchToken(tdb->shortLabel,word->name)
+    &&  !searchMatchToken(tdb->longLabel, word->name))
+        return FALSE;
+    }
+return TRUE;
+}
+
+boolean searchDescriptionMatches(struct trackDb *tdb, struct slName *wordList)
+// returns TRUE if all words in preparsed list matches html description page.
+// A "word" can be "multiple words" (parsed from quoteed string).
+// Because description contains html, quoted string match has limits.
+// DANGER: this will alter html of tdb struct (replacing \n with ' ',
+// so the html should not be displayed after.
+{
+if (tdb->html == NULL)
+    return (wordList != NULL);
+
+if (strchr(tdb->html,'\n'))           // DANGER: don't own memory.
+    strSwapChar(tdb->html,'\n',' ');  //  However, this CGI will use html for no other purpose
+
+struct slName *word = wordList;
+for (; word != NULL; word = word->next)
+    {
+    if (!searchMatchToken(tdb->html,word->name))
+        return FALSE;
+    }
+return TRUE;
+}
+