af3a143571e5aa064eab75c34f9444b35413b562 chmalee Tue Nov 30 15:28:15 2021 -0800 Add snippet support to trix searching. Required changing the wordPos from the first highest matching wordIndex to the wordIndex of the actual span. Have trixContextIndex create a second level index for fast retrieval of line offsets in original text file used by ixIxx. Create a simple UI for navigating hgFind search results. diff --git src/hg/searchExample/searchExample.c src/hg/searchExample/searchExample.c new file mode 100644 index 0000000..d45ff71 --- /dev/null +++ src/hg/searchExample/searchExample.c @@ -0,0 +1,710 @@ +/* searchExample - Example search page using sphinx. */ +#include "common.h" +#include "linefile.h" +#include "hash.h" +#include "options.h" +#include "jksql.h" +#include "htmshell.h" +#include "web.h" +#include "cheapcgi.h" +#include "cart.h" +#include "hui.h" +#include "udc.h" +#include "knetUdc.h" +#include "hgConfig.h" +#include "jsHelper.h" +#include "errCatch.h" +#include "hgFind.h" +#include "cartJson.h" +#include "trackHub.h" +#include "hubConnect.h" +#include "jsonWrite.h" +#include "hgFind.h" +#include "trix.h" +#include "genbank.h" +#include "cartTrackDb.h" + +// name of the searchBar form variable from the HTML +#define SEARCH_TERM_VAR "searchString" +#define SEARCH_LIMIT 100 + +/* Global Variables */ +struct cart *cart; /* CGI and other variables */ +struct hash *oldVars = NULL; +static struct hash *hubLabelHash = NULL; +boolean measureTiming = FALSE; + +struct trackDb *tdbList = NULL; +struct grp *grpList = NULL; +struct hash *trackHash = NULL; +struct hash *groupHash = NULL; + +/* struct hubLabel: a helper struct for making links to hubs in the search result list */ +struct hubLabel + { + char *shortLabel; + char *longLabel; + char *hubId; + }; + +/* Helper functions for cart handlers */ + +static void getLabelsForHubs() +/* Hash up the shortLabels, longLabels and hub_id for a hubUrl */ +{ +if (hubLabelHash != NULL) + return; +hubLabelHash = hashNew(0); +struct sqlConnection *conn = hConnectCentral(); +char **row; +struct sqlResult *sr; +char query[2048]; +sqlSafef(query, sizeof(query), "select hp.hubUrl, hp.shortLabel, hp.longLabel, concat('hub_',id) from hubPublic hp join hubStatus hs on hp.hubUrl=hs.hubUrl"); +sr = sqlGetResult(conn, query); +while ( (row = sqlNextRow(sr)) != NULL) + { + struct hubLabel *label = NULL; + AllocVar(label); + label->shortLabel = cloneString(row[1]); + label->longLabel = cloneString(row[2]); + label->hubId = cloneString(row[3]); + char *hubUrl = cloneString(row[0]); + hashAdd(hubLabelHash, hubUrl, label); + } +hDisconnectCentral(&conn); +} + +static struct hubLabel *getLabelForHub(char *hubUrl) +/* Look up the shortLabel, longLabel, and hub_id for a hubUrl */ +{ +if (!hubLabelHash) + getLabelsForHubs(); +return (struct hubLabel *)hashFindVal(hubLabelHash, hubUrl); +} + +static struct trackDb *addSupers(struct trackDb *trackList) +/* Insert supertracks into the hierarchy. */ +{ +struct trackDb *newList = NULL; +struct trackDb *tdb, *nextTdb; +struct hash *superHash = newHash(5); + +for(tdb = trackList; tdb; tdb = nextTdb) + { + nextTdb = tdb->next; + if (tdb->parent) + { + // part of a super track + if (hashLookup(superHash, tdb->parent->track) == NULL) + { + hashStore(superHash, tdb->parent->track); + + slAddHead(&newList, tdb->parent); + } + slAddTail(&tdb->parent->subtracks, tdb); + } + else + slAddHead(&newList, tdb); + } +slReverse(&newList); +return newList; +} + +void hashTdbNames(struct trackDb *tdb, struct hash *trackHash) +/* Store the track names for lookup, except for knownGene which gets its own special + * category in the UI */ +{ +struct trackDb *tmp; +for (tmp = tdb; tmp != NULL; tmp = tmp->next) + { + if (tmp->subtracks) + hashTdbNames(tmp->subtracks, trackHash); + if (!sameString(tmp->table, tmp->track)) + hashAdd(trackHash, tmp->track, tmp); + hashAdd(trackHash, tmp->table, tmp); + } +} + +void hashTracksAndGroups(struct cart *cart, char *db) +/* get the list of tracks available for this assembly, along with their group names + * and visibility-ness. Note that this implicitly makes connected hubs show up + * in the trackList struct, which means we get item search for connected + * hubs for free */ +{ +if (tdbList != NULL && grpList != NULL) + return; + +if (!trackHash) + trackHash = hashNew(0); +if (!groupHash) + groupHash = hashNew(0); +cartTrackDbInit(cart, &tdbList, &grpList, FALSE); +if (!tdbList) + errAbort("Error getting tracks for %s", db); +if (!grpList) + errAbort("Error getting groups for %s", db); +struct trackDb *superList = addSupers(tdbList); +tdbList = superList; +hashTdbNames(superList, trackHash); +struct grp *g; +for (g = grpList; g != NULL; g = g->next) + if (!sameString(g->name, "allTracks") && !sameString(g->name, "allTables")) + hashStore(groupHash, g->name); +} + +boolean doTrixQuery(struct searchCategory *category, char *searchTerm, struct hgPositions *hgp, char *database, boolean measureTiming) +/* Get a trix search result and snippets for a trix index. + * TODO: return an error message if there is a problem with the trix index or snippet index */ +{ +long startTime = clock1000(); +boolean ret = FALSE; +char *lowered = cloneString(searchTerm); +char *keyWords[16]; +int keyCount; +tolowers(lowered); +keyCount = chopLine(lowered, keyWords); +// TODO: let the user control this: +int maxReturn = SEARCH_LIMIT; +struct trixSearchResult *tsrList = NULL; +if (category->trix) + tsrList = trixSearch(category->trix, keyCount, keyWords, tsmExpand); +struct trixSearchResult *dbTsrList = NULL; +if (sameString(category->name, "publicHubs")) + { + int len = 0; + struct trixSearchResult *tsr, *next; + for (tsr = tsrList; tsr != NULL; tsr = next) + { + next = tsr->next; + char *itemId[5]; + int numItems = chopString(cloneString(tsr->itemId), ":", itemId, ArraySize(itemId)); + if (numItems <= 2 || isEmpty(itemId[2]) || !sameString(itemId[2], database)) + continue; + else + { + slAddHead(&dbTsrList, tsr); + len++; + } + if (len > SEARCH_LIMIT) + break; + } + } +else + dbTsrList = tsrList; + +if (slCount(dbTsrList) > SEARCH_LIMIT) + { + struct trixSearchResult *tsr = slElementFromIx(dbTsrList, maxReturn-1); + tsr->next = NULL; + } +if (dbTsrList != NULL) + { + struct errCatch *errCatch = errCatchNew(); + if (errCatchStart(errCatch)) + addSnippetsToSearchResults(dbTsrList, category->trix); + errCatchEnd(errCatch); + // silently return if there was a problem + if (errCatch->gotError || errCatch->gotWarning) + return FALSE; + errCatchFree(&errCatch); + struct trixSearchResult *tsr = NULL; + struct hgPosTable *table = NULL; + AllocVar(table); + table->name = category->name; + table->description = category->description; + slReverse(&hgp->tableList); + slAddHead(&hgp->tableList, table); + slReverse(&hgp->tableList); + for (tsr = dbTsrList; tsr != NULL; tsr = tsr->next) + { + struct hgPos *this = NULL; + AllocVar(this); + this->name = tsr->itemId; + this->description = tsr->snippet; + if (startsWith(category->name, "trackDb")) + { + struct trackDb *tdb = (struct trackDb *)hashFindVal(trackHash, this->name); + struct dyString *tdbLabels = dyStringNew(0); + dyStringPrintf(tdbLabels, "%s:%s:%s", tsr->itemId, tdb->shortLabel, tdb->longLabel); + this->name = dyStringCannibalize(&tdbLabels); + } + if (sameString(category->name, "publicHubs")) + { + char *itemId[5]; + int numItems = chopString(tsr->itemId, ":", itemId, ArraySize(itemId)); + struct dyString *hubLabel = dyStringNew(0); + char hubUrl[PATH_LEN]; + safef(hubUrl, sizeof(hubUrl), "%s:%s", itemId[0], itemId[1]); + struct hubLabel *label = getLabelForHub(hubUrl); + if (!label) + continue; + char *db = ""; + struct dyString *track = dyStringNew(0); + if (numItems > 2) + db = itemId[2] != NULL ? itemId[2] : ""; + if (numItems > 3) + dyStringPrintf(track, "%s_%s", label->hubId, itemId[3]); + dyStringPrintf(hubLabel, "%s:%s:%s:%s:%s", hubUrl, db, dyStringCannibalize(&track), label->shortLabel, label->longLabel); + this->name = dyStringCannibalize(&hubLabel); + } + slAddHead(&table->posList, this); + } + slReverse(&table->posList); + if (measureTiming) + table->searchTime = clock1000() - startTime; + ret = TRUE; + } +return ret; +} + +boolean addHubsAndDocsToHgp(struct hgPositions *hgp, struct searchCategory *categoryList, char *searchTerm, char *database, boolean measureTiming) +/* Go out and get any results from the public hubs search or help docs search + * and make a fake hgp for them */ +{ +struct searchCategory *category= NULL; +boolean found = FALSE; +getLabelsForHubs(); +for (category = categoryList; category != NULL; category = category->next) + found |= doTrixQuery(category, searchTerm, hgp, database, measureTiming); +return found; +} + +void doQuery(struct jsonWrite *jw, char *db, struct searchCategory *categories, char *searchTerms, boolean measureTiming) +/* Fire off a query. If the query results in a single position and we came from another CGI, + * redirect right back to that CGI (except if we came from hgGateway redirect to hgTracks), + * otherwise return the position list as JSON */ +{ +struct hgPositions *hgp = NULL; +struct searchCategory *category, *trackCategories = NULL, *nonTrackCategories = NULL; +struct searchCategory *next = NULL; +for (category = categories; category != NULL; category = next) + { + next = category->next; + if (!startsWith("trackDb", category->id) && !sameString(category->id, "helpDocs") && !sameString(category->id, "publicHubs")) + slAddHead(&trackCategories, category); + else + slAddHead(&nonTrackCategories, category); + } +if (trackCategories) + hgp = hgPositionsFind(db, searchTerms, "", "searchExample", cart, FALSE, measureTiming, trackCategories); +if (nonTrackCategories) + { + if (!hgp) + AllocVar(hgp); + (void)addHubsAndDocsToHgp(hgp, nonTrackCategories, searchTerms, db, measureTiming); + } +if (hgp) + { + // check if user entered a plain position range, in which case we have to write the + // json ourselves + if (hgp->singlePos && hgp->posCount == 1 && hgp->tableList != NULL && + sameString(hgp->tableList->name, "chromInfo")) + { + jsonWriteListStart(jw, "positionMatches"); + jsonWriteObjectStart(jw, NULL); + jsonWriteString(jw, "name", "chromInfo"); + jsonWriteString(jw, "chrom", hgp->singlePos->chrom); + jsonWriteNumber(jw, "chromStart", hgp->singlePos->chromStart); + jsonWriteNumber(jw, "chromEnd", hgp->singlePos->chromEnd); + jsonWriteObjectEnd(jw); + jsonWriteListEnd(jw); + } + else + hgPositionsJson(jw, db, hgp, cart); + } +} + +//static struct searchCategory *getCategsFromResults(struct jsonElement *results, char *db) +/* From the list of search results, figure out what categories were + * included, and make them default selected */ +/* +{ +// first parse the results for all the trackNames: +struct hash *searchedCategs = hashNew(0); +struct hash *wrapperObj = results->val.jeHash; +struct slRef *result, *resultList = ((struct jsonElement *)hashFindVal(wrapperObj, "positionMatches"))->val.jeList; +for (result = resultList; result != NULL; result = result->next) + { + struct jsonElement *resultObject = (struct jsonElement *)result->val; + char *categName = jsonStringField(resultObject, "trackName"); + if (categName) + hashStoreName(searchedCategs, categName); + } + +// now change the visibility-ness in the final categ list from what +// we already searched: +struct searchCategory *categ, *ret = getCategsForDatabase(cart, db, trackHash, groupHash); +for (categ = ret; categ != NULL; categ = categ->next) + { + if (hashLookup(searchedCategs, categ->id) != NULL) + categ->visibility = 1; + } +struct searchCategory *next, *nonTrackCategs = getCategsForNonDb(cart, db, trackHash, groupHash); +for (categ = nonTrackCategs; categ != NULL; categ = next) + { + next = categ->next; + categ->next = NULL; + if (hashLookup(searchedCategs, categ->id) != NULL) + categ->visibility = 1; + else + categ->visibility = 0; + slAddHead(&ret, categ); + } +return ret; +} +*/ + +static void addCategoryFieldsToHash(struct hash *elementHash, struct searchCategory *category) +{ +hashAdd(elementHash, "name", newJsonString(category->name)); +hashAdd(elementHash, "id", newJsonString(category->id)); +hashAdd(elementHash, "visibility", newJsonNumber((long)category->visibility)); +hashAdd(elementHash, "group", newJsonString(category->groupName)); +hashAdd(elementHash, "label", newJsonString(category->label)); +hashAdd(elementHash, "description", newJsonString(category->description)); +hashAdd(elementHash, "parents", newJsonString(slNameListToString(category->parents, ','))); +hashAdd(elementHash, "priority", newJsonDouble(category->priority)); +} + +static boolean nonTrackCateg(struct searchCategory *categ) +{ +if (startsWith("publicHubs", categ->id) || + startsWith("helpDocs", categ->id) || + startsWith("trackDb", categ->id) || + startsWith("knownGene", categ->id)) + return TRUE; +return FALSE; +} + +static struct jsonElement *jsonElementFromVisibleCategs(struct searchCategory *categs) +{ +struct searchCategory *categ = NULL; +struct jsonElement *ret = newJsonList(NULL); +for (categ = categs; categ != NULL; categ = categ->next) + { + if (categ->visibility > 0) + jsonListAdd(ret, newJsonString(categ->id)); + } +return ret; +} + +struct searchCategory *getVisibleCategories(struct searchCategory *categories) +/* Return a list of only the visible categories. Use CloneVar so our original list + * stays alive */ +{ +struct searchCategory *ret = NULL, *categ = NULL; +for (categ = categories; categ != NULL; categ = categ->next) + { + if (categ->visibility > 0) + { + struct searchCategory *temp = (struct searchCategory *)CloneVar(categ); + slAddHead(&ret, temp); + } + } +return ret; +} + +struct jsonElement *makeTrackGroupsJson(char *db) +/* Turn the available track groups for a database into JSON. Needed for + * sorting the categories */ +{ +hashTracksAndGroups(cart, db); +struct jsonElement *retObj = newJsonObject(hashNew(0)); +struct grp *grp; +for (grp = grpList; grp != NULL; grp = grp->next) + { + if (!sameString(grp->name, "allTracks") && !sameString(grp->name, "allTables")) + { + struct jsonElement *grpObj = newJsonObject(hashNew(0)); + jsonObjectAdd(grpObj, "name", newJsonString(grp->name)); + jsonObjectAdd(grpObj, "label", newJsonString(grp->label)); + jsonObjectAdd(grpObj, "priority", newJsonDouble(grp->priority)); + jsonObjectAdd(retObj, grp->name, grpObj); + } + } +return retObj; +} + +static struct jsonElement *jsonElementFromSearchCategory(struct searchCategory *categories, char *db) +/* Turn struct searchCategory into jsonElement */ +{ +struct jsonElement *trackList = newJsonList(NULL); +struct jsonElement *nonTrackList = newJsonList(NULL); +struct searchCategory *categ = NULL; +for (categ = categories; categ != NULL; categ = categ->next) + { + struct hash *eleHash = hashNew(0); + struct jsonElement *categJson = newJsonObject(eleHash); + addCategoryFieldsToHash(eleHash, categ); + + // now add to one of our final lists + if (nonTrackCateg(categ)) + { + jsonListAdd(nonTrackList, categJson); + } + else + { + if (categ->id) + jsonListAdd(trackList, categJson); + } + } + +// we need to enclose the track list categories in a parent category +// for the javascript to function: +struct jsonElement *trackContainerEle = newJsonObject(hashNew(0)); +jsonObjectAdd(trackContainerEle, "id", newJsonString("trackData")); +char name[2048]; +safef(name, sizeof(name), "%s Track Data", db); +jsonObjectAdd(trackContainerEle, "name", newJsonString(name)); +jsonObjectAdd(trackContainerEle, "tracks", trackList); +jsonObjectAdd(trackContainerEle, "label", newJsonString(name)); +jsonObjectAdd(trackContainerEle, "priority", newJsonDouble(5.0)); +jsonObjectAdd(trackContainerEle, "description", newJsonString("Search for track data items")); +jsonListAdd(nonTrackList, trackContainerEle); +return nonTrackList; +} + +int cmpCategories(const void *a, const void *b) +/* Compare two categories for uniquifying */ +{ +struct searchCategory *categA = *(struct searchCategory **)a; +struct searchCategory *categB = *(struct searchCategory **)b; +return strcmp(categA->id, categB->id); +} + +struct searchCategory *makeCategsFromJson(struct jsonElement *searchCategs, char *db) +/* User has selected some categories, parse the JSON into a struct searchCategory */ +{ +if (searchCategs == NULL) + return NULL; +struct searchCategory *ret = NULL; +struct slRef *jsonVal = NULL; +for (jsonVal= searchCategs->val.jeList; jsonVal != NULL; jsonVal = jsonVal->next) + { + struct jsonElement *val = jsonVal->val; + if (val->type != jsonString) + errAbort("makeCategsFromJson: non-string argument value for 'categs'"); + char *categName = val->val.jeString; + struct searchCategory *category = NULL; + if (!sameString(categName, "allTracks")) + category = makeCategory(cart, categName, NULL, db, trackHash, groupHash); + else + { + struct hashEl *hel, *helList = hashElListHash(trackHash); + for (hel = helList; hel != NULL; hel = hel->next) + { + struct trackDb *tdb = hel->val; + if (!sameString(tdb->track, "knownGene") && !sameString(tdb->table, "knownGene")) + slAddHead(&category, makeCategory(cart, tdb->track, NULL, db, trackHash, groupHash)); + } + } + if (category != NULL) + { + if (ret) + slCat(&ret, category); + else + ret = category; + } + } +// the trackHash can contain both a composite track (where makeCategory would make categories +// for each of the subtracks, and a subtrack, where makeCategory just makes a single +// category, which means our final list can contain duplicate categories, so do a +// uniqify here so we only have one category for each category +slUniqify(&ret, cmpCategories, searchCategoryFree); +return ret; +} + +void jsonElementSaveCategoriesToCart(char *db, struct jsonElement *jel) +/* Write the currently selected categories to the cart so on back button navigation + * we only search the selected categories. jel must be a jeList of strings */ +{ +char cartSetting[512]; +safef(cartSetting, sizeof(cartSetting), "hgSearch_categs_%s", db); +struct dyString *cartVal = dyStringNew(0); +struct slRef *categ; +for (categ = jel->val.jeList; categ != NULL; categ = categ->next) + { + struct jsonElement *val = categ->val; + if (val->type != jsonString) + errAbort("saveCategoriesToCart: category is not a string"); + else + dyStringPrintf(cartVal, "%s,", val->val.jeString); + } +cartSetString(cart, cartSetting, dyStringCannibalize(&cartVal)); +} + +static void writeDefaultForDb(struct jsonWrite *jw, char *database) +/* Put up the default view when entering this page for the first time, which basically + * means return the list of searchable stuff. */ +{ +struct searchCategory *defaultCategories = getAllCategories(cart, database, trackHash, groupHash); +struct jsonElement *categsJsonElement = jsonElementFromSearchCategory(defaultCategories, database); +struct jsonElement *selectedCategsJsonList = jsonElementFromVisibleCategs(defaultCategories); +jsonElementSaveCategoriesToCart(database,selectedCategsJsonList); +dyStringPrintf(jw->dy, ", \"categs\": "); +jsonDyStringPrint(jw->dy, categsJsonElement, NULL, -1); +dyStringPrintf(jw->dy, ", \"trackGroups\": "); +jsonDyStringPrint(jw->dy, makeTrackGroupsJson(database), NULL, -1); +} + +void printMainPageIncludes() +{ +webIncludeResourceFile("gb.css"); +webIncludeResourceFile("gbStatic.css"); +webIncludeResourceFile("spectrum.min.css"); +webIncludeResourceFile("hgGtexTrackSettings.css"); +puts(""); +puts(""); +puts(""); +puts(""); +puts("\n"); +jsIncludeFile("utils.js", NULL); +jsIncludeFile("ajax.js", NULL); +jsIncludeFile("lodash.3.10.0.compat.min.js", NULL); +jsIncludeFile("cart.js", NULL); +jsIncludeFile("searchExample.js", NULL); + +// Write the skeleton HTML, which will get filled out by the javascript +webIncludeFile("inc/searchExample.html"); +} + +/* End handler helper functions */ + +/* Handlers for returning JSON to client */ +static void getSearchResults(struct cartJson *cj, struct hash *paramHash) +/* User has entered a term to search, search this term against the selected categories */ +{ +char *db = cartUsualString(cj->cart, "db", hDefaultDb()); +initGenbankTableNames(db); +hashTracksAndGroups(cj->cart, db); +char *searchTerms = cartJsonRequiredParam(paramHash, SEARCH_TERM_VAR, cj->jw, "getSearchResults"); +measureTiming = cartUsualBoolean(cj->cart, "measureTiming", FALSE); +struct jsonElement *searchCategs = hashFindVal(paramHash, "categs"); +struct searchCategory *searchCategoryList = makeCategsFromJson(searchCategs, db); +doQuery(cj->jw, db, searchCategoryList, searchTerms, measureTiming); +fprintf(stderr, "performed query on %s\n", searchTerms); +} + +static void getUiState(struct cartJson *cj, struct hash *paramHash) +/* We haven't seen this database before, return list of all searchable stuff */ +{ +char *db = cartUsualString(cj->cart, "db", hDefaultDb()); +initGenbankTableNames(db); +hashTracksAndGroups(cj->cart, db); +writeDefaultForDb(cj->jw, db); +} + +/* End Handlers */ + +/* Start do commands, functions that dispatch */ +void doSaveCategoriesToCart(struct cartJson *cj, struct hash *paramHash) +/* Save any selected categories to the cart so if the user clicks the back button + * from hgTracks/hgTrackUi/etc we know what they were last looking at */ +{ +char *db = cartUsualString(cj->cart, "db", hDefaultDb()); +struct jsonElement *jel = hashFindVal(paramHash, "categs"); +if (jel == NULL) + { + jsonWriteStringf(cj->jw, "error", + "saveCategoriesToCart: required param 'categs' is missing"); + return; + } +jsonElementSaveCategoriesToCart(db,jel); +} + +void doCartJson() +/* Register functions that return JSON to client */ +{ +struct cartJson *cj = cartJsonNew(cart); +cartJsonRegisterHandler(cj, "getSearchResults", getSearchResults); +cartJsonRegisterHandler(cj, "getUiState", getUiState); +cartJsonRegisterHandler(cj, "saveCategoriesToCart", doSaveCategoriesToCart); +cartJsonExecute(cj); +} + +void doMainPage() +/* Print the basic HTML page and include any necessary Javascript. AJAX calls + * will fill out the page later */ +{ +char *database = NULL; +char *genome = NULL; +getDbAndGenome(cart, &database, &genome, oldVars); +webStartGbNoBanner(cart, database, "Search Disambiguation"); +printMainPageIncludes(); +// Call our init function +jsInlineF("var hgsid='%s';\n", cartSessionId(cart)); +jsInline("searchExample.init();\n"); +webEndGb(); +} + +void doSearchOnly() +/* Send back search results specified in the CGI arguments */ +{ +char *db = NULL; +char *genome = NULL; +getDbAndGenome(cart, &db, &genome, oldVars); +char *userSearch = cartCgiUsualString(cart, "search", NULL); +if (userSearch == NULL || isEmpty(userSearch)) + { + doMainPage(); + return; + } +initGenbankTableNames(db); +hashTracksAndGroups(cart, db); +struct searchCategory *allCategories = getAllCategories(cart, db, trackHash, groupHash); +struct searchCategory *defaultCategories = getVisibleCategories(allCategories); +struct jsonElement *categsJsonElement = jsonElementFromSearchCategory(allCategories, db); +struct jsonElement *selectedCategsJsonList = jsonElementFromVisibleCategs(defaultCategories); + +struct cartJson *cj = cartJsonNew(cart); +jsonElementSaveCategoriesToCart(db,selectedCategsJsonList); +dyStringPrintf(cj->jw->dy, "\"categs\": "); +jsonDyStringPrint(cj->jw->dy, categsJsonElement, NULL,-1); +dyStringPrintf(cj->jw->dy, ", \"trackGroups\": "); +jsonDyStringPrint(cj->jw->dy, makeTrackGroupsJson(db), NULL, -1); +dyStringPrintf(cj->jw->dy, ", "); + +measureTiming = cartUsualBoolean(cart, "measureTiming", FALSE); +doQuery(cj->jw, db, defaultCategories, userSearch, measureTiming); +// Now we need to actually spit out the page + json +webStartGbNoBanner(cart, db, "Search Disambiguation"); +printMainPageIncludes(); +jsInlineF("var hgsid='%s';\n", cartSessionId(cart)); +jsInline("var cartJson = {"); +jsInline(cj->jw->dy->string); +jsInline("};\n"); +jsInline("searchExample.init();\n"); +webEndGb(); +} +/* End do commands */ + +void doMiddle(struct cart *theCart) +/* Set up globals and make web page */ +{ +cart = theCart; + +if (cgiOptionalString(CARTJSON_COMMAND)) + doCartJson(); +else if (cgiOptionalString("search")) + // we got here from: + // 1. changing the URL arguments + // 2. a back button reload + // regardless, we can just put up the whole page with search results already + // included in the returned HTML + doSearchOnly(); +else + doMainPage(); +} + + +/* Null terminated list of CGI Variables we don't want to save + * permanently. */ +char *excludeVars[] = {"Submit", "submit", NULL,}; + +int main(int argc, char *argv[]) +/* Process command line. */ +{ +cgiSpoof(&argc, argv); +cartEmptyShellNoContent(doMiddle, hUserCookie(), excludeVars, oldVars); +return 0; +}