diff options
| author | Joey Adams | 2010-06-21 23:52:23 +0000 |
|---|---|---|
| committer | Joey Adams | 2010-06-21 23:52:23 +0000 |
| commit | b153d76442ba1ef1237c4cc36f491b3aff7b8510 (patch) | |
| tree | d3d4f0cd8d6d2be370a98b0d13e80dafc6f60bb5 /json.c | |
| parent | f1851e0b1fd7f002b4033d9717ab9a704d402d48 (diff) | |
* Made it so json_path preserves original text.
* Removed the global variable json_escape_unicode and added a parameter for it
to the json_encode and json_encode_string C functions.
Diffstat (limited to 'json.c')
| -rw-r--r-- | json.c | 219 |
1 files changed, 150 insertions, 69 deletions
@@ -26,9 +26,6 @@ #include <ctype.h> - -bool json_escape_unicode = false; - #define JSON_malloc palloc /* repalloc and pfree can't take a null pointer, unlike normal realloc and free. */ @@ -165,6 +162,10 @@ static inline void string_append(String str, const char *append) { string_append_length(str, append, strlen(append)); } +static inline void string_append_range(String str, const char *start, const char *end) +{ + string_append_length(str, start, end - start); +} static inline void string_append_char(String str, char c) { *string_grow(str, 1) = c; @@ -369,6 +370,10 @@ json_node *json_decode(const char *str) const char *s = str; char *key = NULL; size_t key_length = 0; + const char *start = NULL; + const char *key_start = NULL; + const char *key_end = NULL; + const char *value_start = NULL; if (!str) return NULL; @@ -379,11 +384,14 @@ json_node *json_decode(const char *str) goto item; item: /* Expect a value */ + start = s; skip_whitespace(&s); if (parent && parent->type == JSON_OBJECT) { /* Parse member key string. */ + key_start = s; key = json_decode_string(&s, &key_length, true); + key_end = s; if (!key) goto failed; @@ -396,7 +404,15 @@ item: /* Expect a value */ } else { key = NULL; } - + + /* The way node->orig.value_start and node->orig.value_end are initialized + is a little funky. value_start is s (the current position in the JSON text), + so that one's easy. value_end is easy unless we're parsing an object or array. + For an object/array, we have to wait until we're done parsing it + (see state 'endp' ) before we can set value_end. + */ + + value_start = s; node = decode_leaf(&s); if (!node) { if (*s == '[') @@ -406,12 +422,28 @@ item: /* Expect a value */ else goto failed; s++; + + /* node->orig.value_end is dangling (actually NULL) for now, + but will be initialized when we get to state 'endp' . */ + } else { + node->orig.value_end = s; + skip_whitespace(&s); + node->orig.end = s; } + node->orig.used = 1; + node->orig.start = start; + node->orig.value_start = value_start; if (key) { node->key = key; node->key_length = key_length; + + /* The key now belongs to the node. This prevents a double free + on failure (see the failed: label). */ key = NULL; + + node->orig.key_start = key_start; + node->orig.key_end = key_end; } if (parent) @@ -420,6 +452,8 @@ item: /* Expect a value */ root = node; if (is_internal(node)) { + /* "push" node onto the "stack". Nodes point up to their parents, + which is why json_encode, json_decode, etc. don't need a "stack" per se. */ parent = node; goto item_endp; } @@ -430,8 +464,6 @@ item: /* Expect a value */ goto end; comma_endp: /* Expect a comma or end bracket/brace */ - skip_whitespace(&s); - if (*s == ',') { s++; goto item; @@ -451,15 +483,23 @@ endp: /* Handle an end bracket/brace */ if (*s != end_parenthesis(parent)) goto failed; s++; + + /* "pop" a node from the "stack" */ node = parent; parent = parent->parent; + + /* The other pointers were set when we started + parsing this node in the 'item' state. */ + node->orig.value_end = s; + skip_whitespace(&s); + node->orig.end = s; + if (parent) goto comma_endp; else goto end; end: /* Expect end of text */ - skip_whitespace(&s); if (*s) goto failed; return node; @@ -706,7 +746,7 @@ json_type json_text_type(const char *str, size_t nbytes) /****************************** Encoding *****************************/ -static bool encode_string(String out, const char *string, size_t length, char quote) +static bool encode_string(String out, const char *string, size_t length, char quote, bool escape_unicode) { const char *s = string; const char *e = s + length; @@ -732,7 +772,7 @@ static bool encode_string(String out, const char *string, size_t length, char qu e = quote; break; } - if (c < 0x1F || (c >= 0x80 && json_escape_unicode)) { + if (c < 0x1F || (c >= 0x80 && escape_unicode)) { /* Encode using \u.... */ unsigned int uc, lc; char txt[13]; @@ -799,66 +839,103 @@ static bool encode_number(String out, const char *string) return true; } -char *json_encode(json_node *node) +char *json_encode(json_node *node, int options) { - String(ret); - const char *txt; - json_node *root; + String (ret); + const char *txt; + json_node *root; + bool use_orig_opt = !!(options & JSONOPT_USE_ORIG); + bool escape_unicode = !!(options & JSONOPT_ESCAPE_UNICODE); + + #define use_orig \ + (use_orig_opt && node->orig.used) + #define use_orig_unless(FLAG) \ + (use_orig_opt && node->orig.used && !node->orig.FLAG) + + #define push_orig(START, END) \ + string_append_range(ret, node->orig.START, node->orig.END) if (!node) return NULL; root = node; - - goto begin_nokey; + + goto begin_skip_key; begin: /* Encode entire node, or (if it's an array or object) the beginning of it. */ if (node->key) { - if (!encode_string(ret, node->key, node->key_length, '"')) - goto failed; - string_append_char(ret, ':'); - } - goto begin_nokey; - -begin_nokey: - - txt = NULL; - switch (node->type) { - case JSON_NULL: - txt = "null"; - break; - case JSON_BOOL: - if (node->v.v_bool) - txt = "true"; - else - txt = "false"; - break; - case JSON_STRING: - if (!encode_string(ret, node->v.string.str, node->v.string.length, '"')) - goto failed; - break; - case JSON_NUMBER: - if (!encode_number(ret, node->v.number)) + if (use_orig) + push_orig(start, key_start); + + if (use_orig_unless(key_changed)) { + push_orig(key_start, key_end); + } else { + if (!encode_string(ret, node->key, node->key_length, '"', escape_unicode)) goto failed; - break; - case JSON_ARRAY: - txt = "["; - break; - case JSON_OBJECT: - txt = "{"; - break; - default: - goto failed; + } + + if (use_orig) + push_orig(key_end, value_start); + else + string_append_char(ret, ':'); + } else { + if (use_orig) + push_orig(start, value_start); } - if (txt) - string_append(ret, txt); - - if (is_internal(node) && node->v.children.head) { - node = node->v.children.head; - goto begin; + +begin_skip_key: + + if (use_orig_unless(value_changed)) { + /* if the node or its children haven't been modified, + * and JSONOPT_USE_ORIG has been specified, then the entire + * original node string (other than trailing space) + * will be emitted by this one statement. */ + push_orig(value_start, value_end); + + goto finish_skip_bracket; } else { - goto finish; + txt = NULL; + switch (node->type) { + case JSON_NULL: + txt = "null"; + break; + case JSON_BOOL: + if (node->v.v_bool) + txt = "true"; + else + txt = "false"; + break; + case JSON_STRING: + if (!encode_string( ret, + node->v.string.str, + node->v.string.length, + '"', + escape_unicode)) + goto failed; + break; + case JSON_NUMBER: + if (!encode_number(ret, node->v.number)) + goto failed; + break; + case JSON_ARRAY: + txt = "["; + break; + case JSON_OBJECT: + txt = "{"; + break; + default: + goto failed; + } + if (txt) + string_append(ret, txt); + + if (is_internal(node) && node->v.children.head) { + node = node->v.children.head; + goto begin; + } else { + goto finish; + } } finish: /* Finish a node and move to the next one. */ @@ -867,9 +944,13 @@ finish: /* Finish a node and move to the next one. */ else if (node->type == JSON_OBJECT) string_append_char(ret, '}'); +finish_skip_bracket: if (node == root) goto end; + if (use_orig) + push_orig(value_end, end); + if (node->next) { string_append_char(ret, ','); node = node->next; @@ -889,13 +970,17 @@ end: /* All nodes finished being serialized. */ failed: /* Handle error. */ string_free(ret); return NULL; + + #undef use_orig + #undef use_orig_unless + #undef push_orig } -char *json_encode_string(const char *str, size_t length, char quote) +char *json_encode_string(const char *str, size_t length, char quote, bool escape_unicode) { String(ret); - if (!encode_string(ret, str, length, quote)) { + if (!encode_string(ret, str, length, quote, escape_unicode)) { string_free(ret); return NULL; } @@ -908,20 +993,16 @@ char *json_encode_string(const char *str, size_t length, char quote) bool json_validate_liberal(const char *str) { - json_node *node = json_decode_liberal(str); - if (!node) - return false; - json_delete(node); - return true; -} + char *cleaned = json_cleanup(str); + json_node *node = json_decode(cleaned); + bool ret = !!node; -json_node *json_decode_liberal(const char *str) -{ - char *cleaned = json_cleanup(str); - json_node *node = json_decode(cleaned); + if (node) + json_delete(node); if (cleaned) JSON_free(cleaned); - return node; + + return ret; } char *json_cleanup(const char *str) |
