Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 29 additions & 30 deletions parson.c
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ struct json_array_t {

/* Various */
static char * read_file(const char *filename);
static void remove_comments(char *string, const char *start_token, const char *end_token);
static void remove_comments(char *string);
static char * parson_strndup(const char *string, size_t n);
static char * parson_strdup(const char *string);
static int parson_sprintf(char * s, const char * format, ...);
Expand Down Expand Up @@ -236,38 +236,38 @@ static char * read_file(const char * filename) {
return file_contents;
}

static void remove_comments(char *string, const char *start_token, const char *end_token) {
static void remove_comments(char *string) {
/* Strips block and line comments in a single pass. Handling both styles
at once keeps a comment of one style from being misread as code by the
other (e.g. a block comment opener or a lone quote in a line comment). */
parson_bool_t in_string = PARSON_FALSE, escaped = PARSON_FALSE;
size_t i;
char *ptr = NULL, current_char;
size_t start_token_len = strlen(start_token);
size_t end_token_len = strlen(end_token);
if (start_token_len == 0 || end_token_len == 0) {
return;
}
while ((current_char = *string) != '\0') {
if (current_char == '\\' && !escaped) {
escaped = PARSON_TRUE;
string++;
continue;
} else if (current_char == '\"' && !escaped) {
in_string = !in_string;
} else if (!in_string && strncmp(string, start_token, start_token_len) == 0) {
for(i = 0; i < start_token_len; i++) {
string[i] = ' ';
char *end = NULL;
while (*string != '\0') {
if (in_string) {
if (escaped) {
escaped = PARSON_FALSE;
} else if (*string == '\\') {
escaped = PARSON_TRUE;
} else if (*string == '\"') {
in_string = PARSON_FALSE;
}
string = string + start_token_len;
ptr = strstr(string, end_token);
if (!ptr) {
return;
string++;
} else if (*string == '\"') {
in_string = PARSON_TRUE;
string++;
} else if (string[0] == '/' && string[1] == '*') {
end = strstr(string + 2, "*/");
end = end ? end + 2 : string + strlen(string);
while (string < end) {
*string++ = ' ';
}
for (i = 0; i < (ptr - string) + end_token_len; i++) {
string[i] = ' ';
} else if (string[0] == '/' && string[1] == '/') {
while (*string != '\0' && *string != '\n') {
*string++ = ' ';
}
string = ptr + end_token_len - 1;
} else {
string++;
}
escaped = PARSON_FALSE;
string++;
}
}

Expand Down Expand Up @@ -1405,8 +1405,7 @@ JSON_Value * json_parse_string_with_comments(const char *string) {
if (string_mutable_copy == NULL) {
return NULL;
}
remove_comments(string_mutable_copy, "/*", "*/");
remove_comments(string_mutable_copy, "//", "\n");
remove_comments(string_mutable_copy);
string_mutable_copy_ptr = string_mutable_copy;
result = parse_value((const char**)&string_mutable_copy_ptr, 0);
parson_free(string_mutable_copy);
Expand Down
41 changes: 41 additions & 0 deletions tests.c
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ void test_suite_1(void); /* Test 3 files from json.org + serialization*/
void test_suite_2(JSON_Value *value); /* Test correctness of parsed values */
void test_suite_2_no_comments(void);
void test_suite_2_with_comments(void);
void test_suite_2_mixed_comments(void); /* Test mixing block and line comments */
void test_suite_3(void); /* Test parsing valid and invalid strings */
void test_suite_4(void); /* Test deep copy function */
void test_suite_5(void); /* Test building json values from scratch */
Expand Down Expand Up @@ -127,6 +128,7 @@ int tests_main(int argc, char *argv[]) {
test_suite_1();
test_suite_2_no_comments();
test_suite_2_with_comments();
test_suite_2_mixed_comments();
test_suite_3();
test_suite_4();
test_suite_5();
Expand Down Expand Up @@ -308,6 +310,45 @@ void test_suite_2_with_comments(void) {
json_value_free(root_value);
}

void test_suite_2_mixed_comments(void) {
JSON_Value *root_value = NULL;
JSON_Object *root_object = NULL;

/* A block comment opener inside a line comment must not swallow data */
root_value = json_parse_string_with_comments(
"{\n"
" \"a\": 1, // matches src/*.c\n"
" \"b\": 2, /* required */\n"
" \"c\": 3\n"
"}");
root_object = json_object(root_value);
TEST(root_object != NULL);
TEST(json_object_get_count(root_object) == 3);
TEST(DBL_EQ(json_object_get_number(root_object, "b"), 2));
json_value_free(root_value);

/* A lone quote inside a line comment must not hide a block comment */
root_value = json_parse_string_with_comments(
"{\n"
" \"a\": 1, // 5\" screen\n"
" \"b\": /* px */ 2\n"
"}");
root_object = json_object(root_value);
TEST(root_object != NULL);
TEST(DBL_EQ(json_object_get_number(root_object, "b"), 2));
json_value_free(root_value);

/* A line comment opener inside a block comment is just comment text */
root_value = json_parse_string_with_comments("[1, /* see http://x */ 2]");
TEST(json_array_get_count(json_array(root_value)) == 2);
json_value_free(root_value);

/* Comment markers inside strings are preserved */
root_value = json_parse_string_with_comments("{\"u\": \"http://a/*b*/\"} // done");
TEST(STREQ(json_object_get_string(json_object(root_value), "u"), "http://a/*b*/"));
json_value_free(root_value);
}

void test_suite_3(void) {
/* Testing valid strings */
TEST(json_parse_string("{\"lorem\":\"ipsum\"}") != NULL);
Expand Down