Browse Source

Implement json_prettify, json_prettify_file

PUBLISHED_FROM=73b6363c974425d6ceeed9861bb2de03d97f3b1f
Бобби 8 years ago
parent
commit
72e4156a86
4 changed files with 195 additions and 0 deletions
  1. 21 0
      README.md
  2. 94 0
      frozen.c
  3. 14 0
      frozen.h
  4. 66 0
      unit_test.c

+ 21 - 0
README.md

@@ -327,6 +327,27 @@ int json_vsetf(const char *s, int len, struct json_out *out,
                const char *json_path, const char *json_fmt, va_list ap);
 ```
 
+## `json_prettify()`
+
+```c
+/*
+ * Pretty-print JSON string `s,len` into `out`.
+ * Return number of processed bytes in `s`.
+ */
+int json_prettify(const char *s, int len, struct json_out *out);
+```
+
+## `json_prettify_file()`
+
+```c
+/*
+ * Prettify JSON file `file_name`.
+ * Return number of processed bytes, or negative number of error.
+ * On error, file content is not modified.
+ */
+int json_prettify_file(const char *file_name);
+```
+
 # Contributions
 
 To submit contributions, sign

+ 94 - 0
frozen.c

@@ -1186,3 +1186,97 @@ int json_setf(const char *s, int len, struct json_out *out,
   va_end(ap);
   return result;
 }
+
+struct prettify_data {
+  struct json_out *out;
+  int level;
+  int last_token;
+};
+
+static void indent(struct json_out *out, int level) {
+  while (level-- > 0) out->printer(out, "  ", 2);
+}
+
+static void print_key(struct prettify_data *pd, const char *path,
+                      const char *name, int name_len) {
+  if (pd->last_token != JSON_TYPE_INVALID &&
+      pd->last_token != JSON_TYPE_ARRAY_START &&
+      pd->last_token != JSON_TYPE_OBJECT_START) {
+    pd->out->printer(pd->out, ",", 1);
+  }
+  if (path[0] != '\0') pd->out->printer(pd->out, "\n", 1);
+  indent(pd->out, pd->level);
+  if (path[0] != '\0' && path[strlen(path) - 1] != ']') {
+    pd->out->printer(pd->out, "\"", 1);
+    pd->out->printer(pd->out, name, (int) name_len);
+    pd->out->printer(pd->out, "\"", 1);
+    pd->out->printer(pd->out, ": ", 2);
+  }
+}
+
+static void prettify_cb(void *userdata, const char *name, size_t name_len,
+                        const char *path, const struct json_token *t) {
+  struct prettify_data *pd = (struct prettify_data *) userdata;
+  switch (t->type) {
+    case JSON_TYPE_OBJECT_START:
+    case JSON_TYPE_ARRAY_START:
+      print_key(pd, path, name, name_len);
+      pd->out->printer(pd->out, t->type == JSON_TYPE_ARRAY_START ? "[" : "{",
+                       1);
+      pd->level++;
+      break;
+    case JSON_TYPE_OBJECT_END:
+    case JSON_TYPE_ARRAY_END:
+      pd->level--;
+      if (pd->last_token != JSON_TYPE_INVALID &&
+          pd->last_token != JSON_TYPE_ARRAY_START &&
+          pd->last_token != JSON_TYPE_OBJECT_START) {
+        pd->out->printer(pd->out, "\n", 1);
+        indent(pd->out, pd->level);
+      }
+      pd->out->printer(pd->out, t->type == JSON_TYPE_ARRAY_END ? "]" : "}", 1);
+      break;
+    case JSON_TYPE_NUMBER:
+    case JSON_TYPE_NULL:
+    case JSON_TYPE_TRUE:
+    case JSON_TYPE_FALSE:
+    case JSON_TYPE_STRING:
+      print_key(pd, path, name, name_len);
+      if (t->type == JSON_TYPE_STRING) pd->out->printer(pd->out, "\"", 1);
+      pd->out->printer(pd->out, t->ptr, t->len);
+      if (t->type == JSON_TYPE_STRING) pd->out->printer(pd->out, "\"", 1);
+      break;
+    default:
+      break;
+  }
+  pd->last_token = t->type;
+}
+
+int json_prettify(const char *s, int len, struct json_out *out) WEAK;
+int json_prettify(const char *s, int len, struct json_out *out) {
+  struct prettify_data pd = { out, 0, JSON_TYPE_INVALID };
+  return json_walk(s, len, prettify_cb, &pd);
+}
+
+int json_prettify_file(const char *file_name) WEAK;
+int json_prettify_file(const char *file_name) {
+  int res = -1;
+  char *s = json_fread(file_name);
+  FILE *fp;
+  if (s != NULL && (fp = fopen(file_name, "w")) != NULL) {
+    struct json_out out = JSON_OUT_FILE(fp);
+    res = json_prettify(s, strlen(s), &out);
+    if (res < 0) {
+      /* On error, restore the old content */
+      fclose(fp);
+      fp = fopen(file_name, "w");
+      fseek(fp, 0, SEEK_SET);
+      fwrite(s, 1, strlen(s), fp);
+    } else {
+      fputc('\n', fp);
+    }
+    fclose(fp);
+  }
+  free(s);
+  return res;
+}

+ 14 - 0
frozen.h

@@ -100,6 +100,7 @@ typedef void (*json_walk_callback_t)(void *callback_data, const char *name,
 /*
  * Parse `json_string`, invoking `callback` in a way similar to SAX parsers;
  * see `json_walk_callback_t`.
+ * Return number of processed bytes, or a negative error code.
  */
 int json_walk(const char *json_string, int json_string_length,
               json_walk_callback_t callback, void *callback_data);
@@ -252,6 +253,19 @@ int json_setf(const char *s, int len, struct json_out *out,
 int json_vsetf(const char *s, int len, struct json_out *out,
                const char *json_path, const char *json_fmt, va_list ap);
 
+/*
+ * Pretty-print JSON string `s,len` into `out`.
+ * Return number of processed bytes in `s`.
+ */
+int json_prettify(const char *s, int len, struct json_out *out);
+
+/*
+ * Prettify JSON file `file_name`.
+ * Return number of processed bytes, or negative number of error.
+ * On error, file content is not modified.
+ */
+int json_prettify_file(const char *file_name);
+
 #ifdef __cplusplus
 }
 #endif /* __cplusplus */

+ 66 - 0
unit_test.c

@@ -661,6 +661,15 @@ static const char *test_eos(void) {
   return NULL;
 }
 
+static int compare_file(const char *file_name, const char *s) {
+  int res = -1;
+  char *p = json_fread(file_name);
+  if (p == NULL) return res;
+  res = strcmp(p, s);
+  free(p);
+  return res == 0;
+}
+
 static const char *test_fprintf(void) {
   const char *fname = "a.json";
   char *p;
@@ -784,7 +793,64 @@ static const char *test_json_setf(void) {
   return NULL;
 }
 
+static const char *test_prettify(void) {
+  const char *fname = "a.json";
+  char buf[200];
+
+  {
+    const char *s1 = "{ \"a\":   1, \"b\":2,\"c\":[null,\"aa\",{},true]}";
+    struct json_out out = JSON_OUT_BUF(buf, sizeof(buf));
+    const char *s2 =
+        "{\n  \"a\": 1,\n  \"b\": 2,\n  \"c\": [\n    null,\n    \"aa\",\n    "
+        "{},\n    true\n  ]\n}";
+    ASSERT(json_prettify(s1, strlen(s1), &out) > 0);
+    ASSERT(strcmp(buf, s2) == 0);
+  }
+
+  {
+    remove(fname);
+    ASSERT(json_prettify_file(fname) == -1);
+  }
+
+  {
+    ASSERT(compare_file(fname, "") == -1);
+    json_fprintf(fname, "::");
+    ASSERT(json_prettify_file(fname) == JSON_STRING_INVALID);
+    ASSERT(compare_file(fname, "::\n"));
+    remove(fname);
+  }
+
+  {
+    ASSERT(compare_file(fname, "") == -1);
+    json_fprintf(fname, "{");
+    ASSERT(json_prettify_file(fname) == JSON_STRING_INCOMPLETE);
+    ASSERT(compare_file(fname, "{\n"));
+    remove(fname);
+  }
+
+  {
+    ASSERT(compare_file(fname, "") == -1);
+    json_fprintf(fname, "%d", 123);
+    ASSERT(compare_file(fname, "123\n"));
+    ASSERT(json_prettify_file(fname) > 0);
+    ASSERT(compare_file(fname, "123\n"));
+    remove(fname);
+  }
+
+  {
+    const char *s = "{\n  \"a\": 123\n}\n";
+    ASSERT(compare_file(fname, "") == -1);
+    json_fprintf(fname, "{a:%d}", 123);
+    ASSERT(json_prettify_file(fname) > 0);
+    ASSERT(compare_file(fname, s));
+    remove(fname);
+  }
+
+  return NULL;
+}
+
 static const char *run_all_tests(void) {
+  RUN_TEST(test_prettify);
   RUN_TEST(test_eos);
   RUN_TEST(test_scanf);
   RUN_TEST(test_errors);