8c908f948b09826c6cb4452ee5b282aca41be85e galt Tue Dec 8 21:52:59 2015 -0800 Multi-region (exonMostly). This work allows people to look at virtual chromosomes from a list of regions and then navigate and perform all of the usual functions on it. diff --git src/lib/jsonParse.c src/lib/jsonParse.c index d6d629c..e1d4399 100644 --- src/lib/jsonParse.c +++ src/lib/jsonParse.c @@ -1,760 +1,763 @@ /* jsonParse - routines to parse JSON strings and traverse and pick things out of the * resulting object tree. */ /* Copyright (C) 2014 The Regents of the University of California * See README in this or parent directory for licensing information. */ #include "common.h" #include "hash.h" #include "dystring.h" #include "sqlNum.h" #include "jsonParse.h" static struct jsonElement *newJsonElement(jsonElementType type) // generic constructor for a jsonElement; callers fill in the appropriate value { struct jsonElement *ele; AllocVar(ele); ele->type = type; return ele; } struct jsonElement *newJsonString(char *str) { struct jsonElement *ele = newJsonElement(jsonString); ele->val.jeString = cloneString(str); return ele; } struct jsonElement *newJsonBoolean(boolean val) { struct jsonElement *ele = newJsonElement(jsonBoolean); ele->val.jeBoolean = val; return ele; } struct jsonElement *newJsonNumber(long val) { struct jsonElement *ele = newJsonElement(jsonNumber); ele->val.jeNumber = val; return ele; } struct jsonElement *newJsonDouble(double val) { struct jsonElement *ele = newJsonElement(jsonDouble); ele->val.jeDouble = val; return ele; } struct jsonElement *newJsonObject(struct hash *h) { struct jsonElement *ele = newJsonElement(jsonObject); ele->val.jeHash = h; return ele; } struct jsonElement *newJsonList(struct slRef *list) { struct jsonElement *ele = newJsonElement(jsonList); ele->val.jeList = list; return ele; } struct jsonElement *newJsonNull() { struct jsonElement *ele = newJsonElement(jsonNull); ele->val.jeNull = NULL; return ele; } void jsonObjectAdd(struct jsonElement *h, char *name, struct jsonElement *ele) // Add a new element to a jsonObject; existing values are replaced. { if(h->type != jsonObject) errAbort("jsonObjectAdd called on element with incorrect type (%d)", h->type); hashReplace(h->val.jeHash, name, ele); } void jsonListAdd(struct jsonElement *list, struct jsonElement *ele) { if(list->type != jsonList) errAbort("jsonListAdd called on element with incorrect type (%d)", list->type); -slAddHead(&list->val.jeList, ele); +struct slRef *el; +AllocVar(el); +el->val = ele; +slAddHead(&list->val.jeList, el); } static void skipLeadingSpacesWithPos(char *s, int *posPtr) /* skip leading white space. */ { for (;;) { char c = s[*posPtr]; if (!isspace(c)) return; (*posPtr)++; } } static void getSpecificChar(char c, char *str, int *posPtr) { // get specified char from string or errAbort if(str[*posPtr] != c) errAbort("Unexpected character '%c' (expected '%c') - string position %d\n", str[*posPtr], c, *posPtr); (*posPtr)++; } static char *getString(char *str, int *posPtr) { // read a double-quote delimited string; we handle backslash escaping. // returns allocated string. boolean escapeMode = FALSE; int i; struct dyString *ds = dyStringNew(1024); getSpecificChar('"', str, posPtr); for(i = 0;; i++) { char c = str[*posPtr + i]; if(!c) errAbort("Premature end of string (missing trailing double-quote); string position '%d'", *posPtr); else if(escapeMode) { // We support escape sequences listed in http://www.json.org, // except for Unicode which we cannot support in C-strings switch(c) { case 'b': c = '\b'; break; case 'f': c = '\f'; break; case 'n': c = '\n'; break; case 'r': c = '\r'; break; case 't': c = '\t'; break; case 'u': // Pass through Unicode dyStringAppendC(ds, '\\'); break; default: // we don't need to convert \,/ or " dyStringAppendC(ds, c); break; } dyStringAppendC(ds, c); escapeMode = FALSE; } else if(c == '"') break; else if(c == '\\') escapeMode = TRUE; else { dyStringAppendC(ds, c); escapeMode = FALSE; } } *posPtr += i; getSpecificChar('"', str, posPtr); return dyStringCannibalize(&ds); } static struct jsonElement *jsonParseExpression(char *str, int *posPtr); static struct jsonElement *jsonParseObject(char *str, int *posPtr) { struct hash *h = newHash(5); getSpecificChar('{', str, posPtr); while(str[*posPtr] != '}') { // parse out a name : val pair skipLeadingSpacesWithPos(str, posPtr); char *name = getString(str, posPtr); skipLeadingSpacesWithPos(str, posPtr); getSpecificChar(':', str, posPtr); skipLeadingSpacesWithPos(str, posPtr); hashAdd(h, name, jsonParseExpression(str, posPtr)); skipLeadingSpacesWithPos(str, posPtr); if(str[*posPtr] == ',') (*posPtr)++; else break; } skipLeadingSpacesWithPos(str, posPtr); getSpecificChar('}', str, posPtr); return newJsonObject(h); } static struct jsonElement *jsonParseList(char *str, int *posPtr) { struct slRef *list = NULL; getSpecificChar('[', str, posPtr); while(str[*posPtr] != ']') { struct slRef *e; AllocVar(e); skipLeadingSpacesWithPos(str, posPtr); e->val = jsonParseExpression(str, posPtr); slAddHead(&list, e); skipLeadingSpacesWithPos(str, posPtr); if(str[*posPtr] == ',') (*posPtr)++; else break; } skipLeadingSpacesWithPos(str, posPtr); getSpecificChar(']', str, posPtr); slReverse(&list); return newJsonList(list); } static struct jsonElement *jsonParseString(char *str, int *posPtr) { return newJsonString(getString(str, posPtr)); } static struct jsonElement *jsonParseNumber(char *str, int *posPtr) { int i; boolean integral = TRUE; struct jsonElement *retVal = NULL; for(i = 0;; i++) { char c = str[*posPtr + i]; if(c == 'e' || c == 'E' || c == '.') integral = FALSE; else if(!c || (!isdigit(c) && c != '-')) break; } char *val = cloneStringZ(str + *posPtr, i); *posPtr += i; if(integral) retVal = newJsonNumber(sqlLongLong(val)); else { double d; if(sscanf(val, "%lf", &d)) retVal = newJsonDouble(d); else errAbort("Invalid JSON Double: %s", val); } freez(&val); return retVal; } static boolean startsWithWordAlpha(const char *firstWord, const char *string) /* Return TRUE if string starts with firstWord and then either ends or continues with * non-alpha characters. */ { return startsWith(firstWord, string) && !isalpha(string[strlen(firstWord)]); } #define JSON_KEYWORD_TRUE "true" #define JSON_KEYWORD_FALSE "false" #define JSON_KEYWORD_NULL "null" static struct jsonElement *jsonParseKeyword(char *str, int *posPtr) /* If str+*posPtr starts with a keyword token (true, false, null), return a new * jsonElement for it; otherwise return NULL. */ { char *s = str + *posPtr; if (startsWithWordAlpha(JSON_KEYWORD_TRUE, s)) { *posPtr += strlen(JSON_KEYWORD_TRUE); return newJsonBoolean(TRUE); } if (startsWithWordAlpha(JSON_KEYWORD_FALSE, s)) { *posPtr += strlen(JSON_KEYWORD_FALSE); return newJsonBoolean(FALSE); } if (startsWithWordAlpha(JSON_KEYWORD_NULL, s)) { *posPtr += strlen(JSON_KEYWORD_NULL); return newJsonNull(); } return NULL; } // Maximum number of characters from the current position to display in error message: #define MAX_LEN_FOR_ERROR 100 static struct jsonElement *jsonParseExpression(char *str, int *posPtr) { skipLeadingSpacesWithPos(str, posPtr); char c = str[*posPtr]; struct jsonElement *ele = NULL; if(c == '{') return jsonParseObject(str, posPtr); else if (c == '[') return jsonParseList(str, posPtr); else if (c == '"') return jsonParseString(str, posPtr); else if (isdigit(c) || c == '-') return jsonParseNumber(str, posPtr); else if ((ele = jsonParseKeyword(str, posPtr)) != NULL) return ele; else { const char *s = str + *posPtr; int len = strlen(s); if (len > MAX_LEN_FOR_ERROR) errAbort("Invalid JSON token: %.*s...", MAX_LEN_FOR_ERROR, s); else errAbort("Invalid JSON token: %s", s); } return NULL; } struct jsonElement *jsonParse(char *str) { // parse string into an in-memory json representation int pos = 0; struct jsonElement *ele = jsonParseExpression(str, &pos); skipLeadingSpacesWithPos(str, &pos); if(str[pos]) errAbort("Invalid JSON: unprocessed trailing string at position: %d: %s", pos, str + pos); return ele; } char *jsonStringEscape(char *inString) /* backslash escape a string for use in a double quoted json string. * More conservative than javaScriptLiteralEncode because * some json parsers complain if you escape & or ' */ { char c; int outSize = 0; char *outString, *out, *in; if (inString == NULL) return(cloneString("")); /* Count up how long it will be */ in = inString; while ((c = *in++) != 0) { switch(c) { case '\"': case '\\': case '/': case '\b': case '\f': outSize += 2; break; case '\r': case '\t': case '\n': outSize += 3; break; default: outSize += 1; } } outString = needMem(outSize+1); /* Encode string */ in = inString; out = outString; while ((c = *in++) != 0) { switch(c) { case '\"': case '\\': case '/': case '\b': case '\f': *out++ = '\\'; *out++ = c; break; case '\r': *out++ = '\\'; *out++ = 'r'; break; case '\t': *out++ = '\\'; *out++ = 't'; break; case '\n': *out++ = '\\'; *out++ = 'n'; break; default: *out++ = c; } } *out++ = 0; return outString; } void jsonFindNameRecurse(struct jsonElement *ele, char *jName, struct slName **pList) // Search the JSON tree recursively to find all the values associated to // the name, and add them to head of the list. { switch (ele->type) { case jsonObject: { if(hashNumEntries(ele->val.jeHash)) { struct hashEl *el, *list = hashElListHash(ele->val.jeHash); slSort(&list, hashElCmp); for (el = list; el != NULL; el = el->next) { struct jsonElement *val = el->val; if sameString(el->name, jName) slNameAddHead(pList, jsonStringEscape(val->val.jeString)); jsonFindNameRecurse(val, jName, pList); } hashElFreeList(&list); } break; } case jsonList: { struct slRef *el; if(ele->val.jeList) { for (el = ele->val.jeList; el != NULL; el = el->next) { struct jsonElement *val = el->val; jsonFindNameRecurse(val, jName, pList); } } break; } case jsonString: case jsonBoolean: case jsonNumber: case jsonDouble: case jsonNull: { break; } default: { errAbort("jsonFindNameRecurse; invalid type: %d", ele->type); break; } } } struct slName *jsonFindName(struct jsonElement *json, char *jName) // Search the JSON tree to find all the values associated to the name // and add them to head of the list. { struct slName *list = NULL; jsonFindNameRecurse(json, jName, &list); slReverse(&list); return list; } struct slName *jsonFindNameUniq(struct jsonElement *json, char *jName) // Search the JSON tree to find all the unique values associated to the name // and add them to head of the list. { struct slName *list = NULL; jsonFindNameRecurse(json, jName, &list); slUniqify(&list, slNameCmp, slNameFree); slReverse(&list); return list; } void jsonElementRecurse(struct jsonElement *ele, char *name, boolean isLast, void (*startCallback)(struct jsonElement *ele, char *name, boolean isLast, void *context), // Called at element start void (*endCallback)(struct jsonElement *ele, char *name, boolean isLast, void *context), // Called at element end void *context) /* Recurse through JSON tree calling callback functions with element and context. * Either startCallback or endCallback may be NULL*/ { if (startCallback != NULL) startCallback(ele, name, isLast, context); switch (ele->type) { case jsonObject: { if(hashNumEntries(ele->val.jeHash)) { struct hashEl *el, *list = hashElListHash(ele->val.jeHash); slSort(&list, hashElCmp); for (el = list; el != NULL; el = el->next) { struct jsonElement *val = el->val; jsonElementRecurse(val, el->name, el->next == NULL, startCallback, endCallback, context); } hashElFreeList(&list); } break; } case jsonList: { struct slRef *el; if(ele->val.jeList) { for (el = ele->val.jeList; el != NULL; el = el->next) { struct jsonElement *val = el->val; jsonElementRecurse(val, NULL, el->next == NULL, startCallback, endCallback, context); } } break; } case jsonString: case jsonBoolean: case jsonNumber: case jsonDouble: case jsonNull: { break; } default: { errAbort("jsonElementRecurse; invalid type: %d", ele->type); break; } } if (endCallback != NULL) endCallback(ele, name, isLast, context); } void jsonPrintOneStart(struct jsonElement *ele, char *name, boolean isLast, int indent, FILE *f) /* Print the start of one json element - just name and maybe an opening brace or bracket. * Recursion is handled elsewhere. */ { spaceOut(f, indent); if (name != NULL) { fprintf(f, "\"%s\": ", name); } switch (ele->type) { case jsonObject: { fprintf(f, "{\n"); break; } case jsonList: { fprintf(f, "[\n"); break; } case jsonString: { char *escaped = jsonStringEscape(ele->val.jeString); fprintf(f, "\"%s\"", escaped); freez(&escaped); break; } case jsonBoolean: { char *val = (ele->val.jeBoolean ? "true" : "false"); fprintf(f, "%s", val); break; } case jsonNumber: { fprintf(f, "%ld", ele->val.jeNumber); break; } case jsonDouble: { fprintf(f, "%g", ele->val.jeDouble); break; } case jsonNull: { fprintf(f, "null"); break; } default: { errAbort("jsonPrintOneStart; invalid type: %d", ele->type); break; } } } void jsonPrintOneEnd(struct jsonElement *ele, char *name, boolean isLast, boolean indent, FILE *f) /* Print object end */ { switch (ele->type) { case jsonObject: { spaceOut(f, indent); fprintf(f, "}"); break; } case jsonList: { spaceOut(f, indent); fprintf(f, "]"); break; } case jsonString: case jsonBoolean: case jsonNumber: case jsonDouble: case jsonNull: break; default: { errAbort("jsonPrintOneEnd; invalid type: %d", ele->type); break; } } if (!isLast) fputc(',', f); fputc('\n', f); } struct jsonPrintContext /* Context for printing a JSON object nicely */ { FILE *f; // where to print it int indent; // How much to indent currently int indentPer; // How much to indent each level }; static void printIndentedNameStartCallback(struct jsonElement *ele, char *name, boolean isLast, void *context) { struct jsonPrintContext *jps = context; jsonPrintOneStart(ele, name, isLast, jps->indent, jps->f); jps->indent += jps->indentPer; } static void printIndentedNameEndCallback(struct jsonElement *ele, char *name, boolean isLast, void *context) { struct jsonPrintContext *jps = context; jps->indent -= jps->indentPer; jsonPrintOneEnd(ele, name, isLast, jps->indent, jps->f); } void jsonPrintToFile(struct jsonElement *root, char *name, FILE *f, int indentPer) /* Print out JSON object and all children nicely indented to f as JSON objects. * Name may be NULL. Implemented via jsonPrintOneStart/jsonPrintOneEnd. */ { struct jsonPrintContext jps = {f, 0, indentPer}; jsonElementRecurse(root, NULL, TRUE, printIndentedNameStartCallback, printIndentedNameEndCallback, &jps); } /** Routines that check json type and return corresponding value. **/ struct slRef *jsonListVal(struct jsonElement *ele, char *name) /* Enforce element is type jsonList or jsonNull. Return list value, which may be NULL. */ { if (ele->type == jsonNull) return NULL; else if (ele->type != jsonList) errAbort("json element %s is not a list", name); return ele->val.jeList; } struct hash *jsonObjectVal(struct jsonElement *ele, char *name) /* Enforce object is type jsonObject or jsonNull. Return object hash, which may be NULL. */ { if (ele->type == jsonNull) return NULL; else if (ele->type != jsonObject) errAbort("json element %s is not an object", name); return ele->val.jeHash; } long jsonNumberVal(struct jsonElement *ele, char *name) /* Enforce element is type jsonNumber and return value. */ { if (ele->type != jsonNumber) errAbort("json element %s is not a number", name); return ele->val.jeNumber; } double jsonDoubleVal(struct jsonElement *ele, char *name) /* Enforce element is type jsonDouble and return value. */ { if (ele->type != jsonDouble) errAbort("json element %s is not a number", name); return ele->val.jeDouble; } boolean jsonBooleanVal(struct jsonElement *ele, char *name) /* Enforce element is type jsonBoolean and return value. */ { if (ele->type != jsonBoolean) errAbort("json element %s is not a boolean", name); return ele->val.jeBoolean; } char *jsonStringVal(struct jsonElement *ele, char *eleName) /* Enforce element is type jsonString or jsonNull. Return value, which may be NULL. */ { if (ele->type == jsonNull) return NULL; else if (ele->type != jsonString) errAbort("json element %s is not a string", eleName); return ele->val.jeString; } /** Routines that help work with json objects (bracket enclosed key/val pairs **/ struct jsonElement *jsonFindNamedField(struct jsonElement *object, char *objectName, char *field) /* Find named field of object or return NULL if not found. Abort if object * is not actually an object. */ { struct hash *hash = jsonObjectVal(object, objectName); if (hash == NULL) return NULL; return hashFindVal(hash, field); } struct jsonElement *jsonMustFindNamedField(struct jsonElement *object, char *objectName, char *field) /* Find named field of object or die trying. */ { struct jsonElement *ele = jsonFindNamedField(object, objectName, field); if (ele == NULL) errAbort("Couldn't find field %s in json object %s", field, objectName); return ele; } char *jsonOptionalStringField(struct jsonElement *object, char *field, char *defaultVal) /* Return string valued field of object, or defaultVal if it doesn't exist. */ { struct jsonElement *ele = jsonFindNamedField(object, "", field); if (ele == NULL) return defaultVal; return jsonStringVal(ele, field); } char *jsonStringField(struct jsonElement *object, char *field) /* Return string valued field of object or abort if field doesn't exist. */ { char *val = jsonOptionalStringField(object, field, NULL); if (val == NULL) errAbort("Field %s doesn't exist in json object", field); return val; } boolean jsonOptionalBooleanField(struct jsonElement *object, char *field, boolean defaultVal) /* Return boolean valued field of object, or defaultVal if it doesn't exist. */ { struct jsonElement *ele = jsonFindNamedField(object, "", field); if (ele == NULL) return defaultVal; return jsonBooleanVal(ele, field); }