cmark
My personal build of CMark ✏️
main.c (45225B)
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 5 #define CMARK_NO_SHORT_NAMES 6 #include "cmark.h" 7 #include "node.h" 8 9 #include "harness.h" 10 #include "cplusplus.h" 11 12 #define UTF8_REPL "\xEF\xBF\xBD" 13 14 static const cmark_node_type node_types[] = { 15 CMARK_NODE_DOCUMENT, CMARK_NODE_BLOCK_QUOTE, CMARK_NODE_LIST, 16 CMARK_NODE_ITEM, CMARK_NODE_CODE_BLOCK, CMARK_NODE_HTML_BLOCK, 17 CMARK_NODE_PARAGRAPH, CMARK_NODE_HEADING, CMARK_NODE_THEMATIC_BREAK, 18 CMARK_NODE_TEXT, CMARK_NODE_SOFTBREAK, CMARK_NODE_LINEBREAK, 19 CMARK_NODE_CODE, CMARK_NODE_HTML_INLINE, CMARK_NODE_EMPH, 20 CMARK_NODE_STRONG, CMARK_NODE_LINK, CMARK_NODE_IMAGE}; 21 static const int num_node_types = sizeof(node_types) / sizeof(*node_types); 22 23 static void test_md_to_html(test_batch_runner *runner, const char *markdown, 24 const char *expected_html, const char *msg); 25 26 static void test_content(test_batch_runner *runner, cmark_node_type type, 27 int allowed_content); 28 29 static void test_char(test_batch_runner *runner, int valid, const char *utf8, 30 const char *msg); 31 32 static void test_incomplete_char(test_batch_runner *runner, const char *utf8, 33 const char *msg); 34 35 static void test_continuation_byte(test_batch_runner *runner, const char *utf8); 36 37 static void version(test_batch_runner *runner) { 38 INT_EQ(runner, cmark_version(), CMARK_VERSION, "cmark_version"); 39 STR_EQ(runner, cmark_version_string(), CMARK_VERSION_STRING, 40 "cmark_version_string"); 41 } 42 43 static void constructor(test_batch_runner *runner) { 44 for (int i = 0; i < num_node_types; ++i) { 45 cmark_node_type type = node_types[i]; 46 cmark_node *node = cmark_node_new(type); 47 OK(runner, node != NULL, "new type %d", type); 48 INT_EQ(runner, cmark_node_get_type(node), type, "get_type %d", type); 49 50 switch (node->type) { 51 case CMARK_NODE_HEADING: 52 INT_EQ(runner, cmark_node_get_heading_level(node), 1, 53 "default heading level is 1"); 54 node->as.heading.level = 1; 55 break; 56 57 case CMARK_NODE_LIST: 58 INT_EQ(runner, cmark_node_get_list_type(node), CMARK_BULLET_LIST, 59 "default is list type is bullet"); 60 INT_EQ(runner, cmark_node_get_list_delim(node), CMARK_NO_DELIM, 61 "default is list delim is NO_DELIM"); 62 INT_EQ(runner, cmark_node_get_list_start(node), 0, 63 "default is list start is 0"); 64 INT_EQ(runner, cmark_node_get_list_tight(node), 0, 65 "default is list is loose"); 66 break; 67 68 default: 69 break; 70 } 71 72 cmark_node_free(node); 73 } 74 } 75 76 static void accessors(test_batch_runner *runner) { 77 static const char markdown[] = "## Header\n" 78 "\n" 79 "* Item 1\n" 80 "* Item 2\n" 81 "\n" 82 "2. Item 1\n" 83 "\n" 84 "3. Item 2\n" 85 "\n" 86 "``` lang\n" 87 "fenced\n" 88 "```\n" 89 " code\n" 90 "\n" 91 "<div>html</div>\n" 92 "\n" 93 "[link](url 'title')\n"; 94 95 cmark_node *doc = 96 cmark_parse_document(markdown, sizeof(markdown) - 1, CMARK_OPT_DEFAULT); 97 98 // Getters 99 100 cmark_node *heading = cmark_node_first_child(doc); 101 INT_EQ(runner, cmark_node_get_heading_level(heading), 2, "get_heading_level"); 102 103 cmark_node *bullet_list = cmark_node_next(heading); 104 INT_EQ(runner, cmark_node_get_list_type(bullet_list), CMARK_BULLET_LIST, 105 "get_list_type bullet"); 106 INT_EQ(runner, cmark_node_get_list_tight(bullet_list), 1, 107 "get_list_tight tight"); 108 109 cmark_node *ordered_list = cmark_node_next(bullet_list); 110 INT_EQ(runner, cmark_node_get_list_type(ordered_list), CMARK_ORDERED_LIST, 111 "get_list_type ordered"); 112 INT_EQ(runner, cmark_node_get_list_delim(ordered_list), CMARK_PERIOD_DELIM, 113 "get_list_delim ordered"); 114 INT_EQ(runner, cmark_node_get_list_start(ordered_list), 2, "get_list_start"); 115 INT_EQ(runner, cmark_node_get_list_tight(ordered_list), 0, 116 "get_list_tight loose"); 117 118 cmark_node *fenced = cmark_node_next(ordered_list); 119 STR_EQ(runner, cmark_node_get_literal(fenced), "fenced\n", 120 "get_literal fenced code"); 121 STR_EQ(runner, cmark_node_get_fence_info(fenced), "lang", "get_fence_info"); 122 123 cmark_node *code = cmark_node_next(fenced); 124 STR_EQ(runner, cmark_node_get_literal(code), "code\n", 125 "get_literal indented code"); 126 127 cmark_node *html = cmark_node_next(code); 128 STR_EQ(runner, cmark_node_get_literal(html), "<div>html</div>\n", 129 "get_literal html"); 130 131 cmark_node *paragraph = cmark_node_next(html); 132 INT_EQ(runner, cmark_node_get_start_line(paragraph), 17, "get_start_line"); 133 INT_EQ(runner, cmark_node_get_start_column(paragraph), 1, "get_start_column"); 134 INT_EQ(runner, cmark_node_get_end_line(paragraph), 17, "get_end_line"); 135 136 cmark_node *link = cmark_node_first_child(paragraph); 137 STR_EQ(runner, cmark_node_get_url(link), "url", "get_url"); 138 STR_EQ(runner, cmark_node_get_title(link), "title", "get_title"); 139 140 cmark_node *string = cmark_node_first_child(link); 141 STR_EQ(runner, cmark_node_get_literal(string), "link", "get_literal string"); 142 143 // Setters 144 145 OK(runner, cmark_node_set_heading_level(heading, 3), "set_heading_level"); 146 147 OK(runner, cmark_node_set_list_type(bullet_list, CMARK_ORDERED_LIST), 148 "set_list_type ordered"); 149 OK(runner, cmark_node_set_list_delim(bullet_list, CMARK_PAREN_DELIM), 150 "set_list_delim paren"); 151 OK(runner, cmark_node_set_list_start(bullet_list, 3), "set_list_start"); 152 OK(runner, cmark_node_set_list_tight(bullet_list, 0), "set_list_tight loose"); 153 154 OK(runner, cmark_node_set_list_type(ordered_list, CMARK_BULLET_LIST), 155 "set_list_type bullet"); 156 OK(runner, cmark_node_set_list_tight(ordered_list, 1), 157 "set_list_tight tight"); 158 159 OK(runner, cmark_node_set_literal(code, "CODE\n"), 160 "set_literal indented code"); 161 162 OK(runner, cmark_node_set_literal(fenced, "FENCED\n"), 163 "set_literal fenced code"); 164 OK(runner, cmark_node_set_fence_info(fenced, "LANG"), "set_fence_info"); 165 166 OK(runner, cmark_node_set_literal(html, "<div>HTML</div>\n"), 167 "set_literal html"); 168 169 OK(runner, cmark_node_set_url(link, "URL"), "set_url"); 170 OK(runner, cmark_node_set_title(link, "TITLE"), "set_title"); 171 172 OK(runner, cmark_node_set_literal(string, "prefix-LINK"), 173 "set_literal string"); 174 175 // Set literal to suffix of itself (issue #139). 176 const char *literal = cmark_node_get_literal(string); 177 OK(runner, cmark_node_set_literal(string, literal + sizeof("prefix")), 178 "set_literal suffix"); 179 180 char *rendered_html = cmark_render_html(doc, 181 CMARK_OPT_DEFAULT | CMARK_OPT_UNSAFE); 182 static const char expected_html[] = 183 "<h3>Header</h3>\n" 184 "<ol start=\"3\">\n" 185 "<li>\n" 186 "<p>Item 1</p>\n" 187 "</li>\n" 188 "<li>\n" 189 "<p>Item 2</p>\n" 190 "</li>\n" 191 "</ol>\n" 192 "<ul>\n" 193 "<li>Item 1</li>\n" 194 "<li>Item 2</li>\n" 195 "</ul>\n" 196 "<pre><code class=\"language-LANG\">FENCED\n" 197 "</code></pre>\n" 198 "<pre><code>CODE\n" 199 "</code></pre>\n" 200 "<div>HTML</div>\n" 201 "<p><a href=\"URL\" title=\"TITLE\">LINK</a></p>\n"; 202 STR_EQ(runner, rendered_html, expected_html, "setters work"); 203 free(rendered_html); 204 205 // Getter errors 206 207 INT_EQ(runner, cmark_node_get_heading_level(bullet_list), 0, 208 "get_heading_level error"); 209 INT_EQ(runner, cmark_node_get_list_type(heading), CMARK_NO_LIST, 210 "get_list_type error"); 211 INT_EQ(runner, cmark_node_get_list_start(code), 0, "get_list_start error"); 212 INT_EQ(runner, cmark_node_get_list_tight(fenced), 0, "get_list_tight error"); 213 OK(runner, cmark_node_get_literal(ordered_list) == NULL, "get_literal error"); 214 OK(runner, cmark_node_get_fence_info(paragraph) == NULL, 215 "get_fence_info error"); 216 OK(runner, cmark_node_get_url(html) == NULL, "get_url error"); 217 OK(runner, cmark_node_get_title(heading) == NULL, "get_title error"); 218 219 // Setter errors 220 221 OK(runner, !cmark_node_set_heading_level(bullet_list, 3), 222 "set_heading_level error"); 223 OK(runner, !cmark_node_set_list_type(heading, CMARK_ORDERED_LIST), 224 "set_list_type error"); 225 OK(runner, !cmark_node_set_list_start(code, 3), "set_list_start error"); 226 OK(runner, !cmark_node_set_list_tight(fenced, 0), "set_list_tight error"); 227 OK(runner, !cmark_node_set_literal(ordered_list, "content\n"), 228 "set_literal error"); 229 OK(runner, !cmark_node_set_fence_info(paragraph, "lang"), 230 "set_fence_info error"); 231 OK(runner, !cmark_node_set_url(html, "url"), "set_url error"); 232 OK(runner, !cmark_node_set_title(heading, "title"), "set_title error"); 233 234 OK(runner, !cmark_node_set_heading_level(heading, 0), 235 "set_heading_level too small"); 236 OK(runner, !cmark_node_set_heading_level(heading, 7), 237 "set_heading_level too large"); 238 OK(runner, !cmark_node_set_list_type(bullet_list, CMARK_NO_LIST), 239 "set_list_type invalid"); 240 OK(runner, !cmark_node_set_list_start(bullet_list, -1), 241 "set_list_start negative"); 242 243 cmark_node_free(doc); 244 } 245 246 static void free_parent(test_batch_runner *runner) { 247 static const char markdown[] = "text\n"; 248 249 cmark_node *doc = 250 cmark_parse_document(markdown, sizeof(markdown) - 1, CMARK_OPT_DEFAULT); 251 252 cmark_node *para = cmark_node_first_child(doc); 253 cmark_node *text = cmark_node_first_child(para); 254 cmark_node_unlink(text); 255 cmark_node_free(doc); 256 STR_EQ(runner, cmark_node_get_literal(text), "text", 257 "inline content after freeing parent block"); 258 cmark_node_free(text); 259 } 260 261 static void node_check(test_batch_runner *runner) { 262 // Construct an incomplete tree. 263 cmark_node *doc = cmark_node_new(CMARK_NODE_DOCUMENT); 264 cmark_node *p1 = cmark_node_new(CMARK_NODE_PARAGRAPH); 265 cmark_node *p2 = cmark_node_new(CMARK_NODE_PARAGRAPH); 266 doc->first_child = p1; 267 p1->next = p2; 268 269 INT_EQ(runner, cmark_node_check(doc, NULL), 4, "node_check works"); 270 INT_EQ(runner, cmark_node_check(doc, NULL), 0, "node_check fixes tree"); 271 272 cmark_node_free(doc); 273 } 274 275 static void iterator(test_batch_runner *runner) { 276 cmark_node *doc = cmark_parse_document("> a *b*\n\nc", 10, CMARK_OPT_DEFAULT); 277 int parnodes = 0; 278 cmark_event_type ev_type; 279 cmark_iter *iter = cmark_iter_new(doc); 280 cmark_node *cur; 281 282 while ((ev_type = cmark_iter_next(iter)) != CMARK_EVENT_DONE) { 283 cur = cmark_iter_get_node(iter); 284 if (cur->type == CMARK_NODE_PARAGRAPH && ev_type == CMARK_EVENT_ENTER) { 285 parnodes += 1; 286 } 287 } 288 INT_EQ(runner, parnodes, 2, "iterate correctly counts paragraphs"); 289 290 cmark_iter_free(iter); 291 cmark_node_free(doc); 292 } 293 294 static void iterator_delete(test_batch_runner *runner) { 295 static const char md[] = "a *b* c\n" 296 "\n" 297 "* item1\n" 298 "* item2\n" 299 "\n" 300 "a `b` c\n" 301 "\n" 302 "* item1\n" 303 "* item2\n"; 304 cmark_node *doc = cmark_parse_document(md, sizeof(md) - 1, CMARK_OPT_DEFAULT); 305 cmark_iter *iter = cmark_iter_new(doc); 306 cmark_event_type ev_type; 307 308 while ((ev_type = cmark_iter_next(iter)) != CMARK_EVENT_DONE) { 309 cmark_node *node = cmark_iter_get_node(iter); 310 // Delete list, emph, and code nodes. 311 if ((ev_type == CMARK_EVENT_EXIT && node->type == CMARK_NODE_LIST) || 312 (ev_type == CMARK_EVENT_EXIT && node->type == CMARK_NODE_EMPH) || 313 (ev_type == CMARK_EVENT_ENTER && node->type == CMARK_NODE_CODE)) { 314 cmark_node_free(node); 315 } 316 } 317 318 char *html = cmark_render_html(doc, CMARK_OPT_DEFAULT); 319 static const char expected[] = "<p>a c</p>\n" 320 "<p>a c</p>\n"; 321 STR_EQ(runner, html, expected, "iterate and delete nodes"); 322 323 cmark_mem *allocator = cmark_get_default_mem_allocator(); 324 325 allocator->free(html); 326 cmark_iter_free(iter); 327 cmark_node_free(doc); 328 } 329 330 static void create_tree(test_batch_runner *runner) { 331 char *html; 332 cmark_node *doc = cmark_node_new(CMARK_NODE_DOCUMENT); 333 334 cmark_node *p = cmark_node_new(CMARK_NODE_PARAGRAPH); 335 OK(runner, !cmark_node_insert_before(doc, p), "insert before root fails"); 336 OK(runner, !cmark_node_insert_after(doc, p), "insert after root fails"); 337 OK(runner, cmark_node_append_child(doc, p), "append1"); 338 INT_EQ(runner, cmark_node_check(doc, NULL), 0, "append1 consistent"); 339 OK(runner, cmark_node_parent(p) == doc, "node_parent"); 340 341 cmark_node *emph = cmark_node_new(CMARK_NODE_EMPH); 342 OK(runner, cmark_node_prepend_child(p, emph), "prepend1"); 343 INT_EQ(runner, cmark_node_check(doc, NULL), 0, "prepend1 consistent"); 344 345 cmark_node *str1 = cmark_node_new(CMARK_NODE_TEXT); 346 cmark_node_set_literal(str1, "Hello, "); 347 OK(runner, cmark_node_prepend_child(p, str1), "prepend2"); 348 INT_EQ(runner, cmark_node_check(doc, NULL), 0, "prepend2 consistent"); 349 350 cmark_node *str3 = cmark_node_new(CMARK_NODE_TEXT); 351 cmark_node_set_literal(str3, "!"); 352 OK(runner, cmark_node_append_child(p, str3), "append2"); 353 INT_EQ(runner, cmark_node_check(doc, NULL), 0, "append2 consistent"); 354 355 cmark_node *str2 = cmark_node_new(CMARK_NODE_TEXT); 356 cmark_node_set_literal(str2, "world"); 357 OK(runner, cmark_node_append_child(emph, str2), "append3"); 358 INT_EQ(runner, cmark_node_check(doc, NULL), 0, "append3 consistent"); 359 360 html = cmark_render_html(doc, CMARK_OPT_DEFAULT); 361 STR_EQ(runner, html, "<p>Hello, <em>world</em>!</p>\n", "render_html"); 362 free(html); 363 364 OK(runner, cmark_node_insert_before(str1, str3), "ins before1"); 365 INT_EQ(runner, cmark_node_check(doc, NULL), 0, "ins before1 consistent"); 366 // 31e 367 OK(runner, cmark_node_first_child(p) == str3, "ins before1 works"); 368 369 OK(runner, cmark_node_insert_before(str1, emph), "ins before2"); 370 INT_EQ(runner, cmark_node_check(doc, NULL), 0, "ins before2 consistent"); 371 // 3e1 372 OK(runner, cmark_node_last_child(p) == str1, "ins before2 works"); 373 374 OK(runner, cmark_node_insert_after(str1, str3), "ins after1"); 375 INT_EQ(runner, cmark_node_check(doc, NULL), 0, "ins after1 consistent"); 376 // e13 377 OK(runner, cmark_node_next(str1) == str3, "ins after1 works"); 378 379 OK(runner, cmark_node_insert_after(str1, emph), "ins after2"); 380 INT_EQ(runner, cmark_node_check(doc, NULL), 0, "ins after2 consistent"); 381 // 1e3 382 OK(runner, cmark_node_previous(emph) == str1, "ins after2 works"); 383 384 cmark_node *str4 = cmark_node_new(CMARK_NODE_TEXT); 385 cmark_node_set_literal(str4, "brzz"); 386 OK(runner, cmark_node_replace(str1, str4), "replace"); 387 // The replaced node is not freed 388 cmark_node_free(str1); 389 390 INT_EQ(runner, cmark_node_check(doc, NULL), 0, "replace consistent"); 391 OK(runner, cmark_node_previous(emph) == str4, "replace works"); 392 INT_EQ(runner, cmark_node_replace(p, str4), 0, "replace str for p fails"); 393 394 cmark_node_unlink(emph); 395 396 html = cmark_render_html(doc, CMARK_OPT_DEFAULT); 397 STR_EQ(runner, html, "<p>brzz!</p>\n", "render_html after shuffling"); 398 free(html); 399 400 cmark_node_free(doc); 401 cmark_node_free(emph); 402 } 403 404 static void custom_nodes(test_batch_runner *runner) { 405 char *html; 406 char *man; 407 cmark_node *doc = cmark_node_new(CMARK_NODE_DOCUMENT); 408 cmark_node *p = cmark_node_new(CMARK_NODE_PARAGRAPH); 409 cmark_node_append_child(doc, p); 410 cmark_node *ci = cmark_node_new(CMARK_NODE_CUSTOM_INLINE); 411 cmark_node *str1 = cmark_node_new(CMARK_NODE_TEXT); 412 cmark_node_set_literal(str1, "Hello"); 413 OK(runner, cmark_node_append_child(ci, str1), "append1"); 414 OK(runner, cmark_node_set_on_enter(ci, "<ON ENTER|"), "set_on_enter"); 415 OK(runner, cmark_node_set_on_exit(ci, "|ON EXIT>"), "set_on_exit"); 416 STR_EQ(runner, cmark_node_get_on_enter(ci), "<ON ENTER|", "get_on_enter"); 417 STR_EQ(runner, cmark_node_get_on_exit(ci), "|ON EXIT>", "get_on_exit"); 418 cmark_node_append_child(p, ci); 419 cmark_node *cb = cmark_node_new(CMARK_NODE_CUSTOM_BLOCK); 420 cmark_node_set_on_enter(cb, "<on enter|"); 421 // leave on_exit unset 422 STR_EQ(runner, cmark_node_get_on_exit(cb), "", "get_on_exit (empty)"); 423 cmark_node_append_child(doc, cb); 424 425 html = cmark_render_html(doc, CMARK_OPT_DEFAULT); 426 STR_EQ(runner, html, "<p><ON ENTER|Hello|ON EXIT></p>\n<on enter|\n", 427 "render_html"); 428 free(html); 429 430 man = cmark_render_man(doc, CMARK_OPT_DEFAULT, 0); 431 STR_EQ(runner, man, ".PP\n<ON ENTER|Hello|ON EXIT>\n<on enter|\n", 432 "render_man"); 433 free(man); 434 435 cmark_node_free(doc); 436 } 437 438 void hierarchy(test_batch_runner *runner) { 439 cmark_node *bquote1 = cmark_node_new(CMARK_NODE_BLOCK_QUOTE); 440 cmark_node *bquote2 = cmark_node_new(CMARK_NODE_BLOCK_QUOTE); 441 cmark_node *bquote3 = cmark_node_new(CMARK_NODE_BLOCK_QUOTE); 442 443 OK(runner, cmark_node_append_child(bquote1, bquote2), "append bquote2"); 444 OK(runner, cmark_node_append_child(bquote2, bquote3), "append bquote3"); 445 OK(runner, !cmark_node_append_child(bquote3, bquote3), 446 "adding a node as child of itself fails"); 447 OK(runner, !cmark_node_append_child(bquote3, bquote1), 448 "adding a parent as child fails"); 449 450 cmark_node_free(bquote1); 451 452 int max_node_type = CMARK_NODE_LAST_BLOCK > CMARK_NODE_LAST_INLINE 453 ? CMARK_NODE_LAST_BLOCK 454 : CMARK_NODE_LAST_INLINE; 455 OK(runner, max_node_type < 32, "all node types < 32"); 456 457 int list_item_flag = 1 << CMARK_NODE_ITEM; 458 int top_level_blocks = 459 (1 << CMARK_NODE_BLOCK_QUOTE) | (1 << CMARK_NODE_LIST) | 460 (1 << CMARK_NODE_CODE_BLOCK) | (1 << CMARK_NODE_HTML_BLOCK) | 461 (1 << CMARK_NODE_PARAGRAPH) | (1 << CMARK_NODE_HEADING) | 462 (1 << CMARK_NODE_THEMATIC_BREAK); 463 int all_inlines = (1 << CMARK_NODE_TEXT) | (1 << CMARK_NODE_SOFTBREAK) | 464 (1 << CMARK_NODE_LINEBREAK) | (1 << CMARK_NODE_CODE) | 465 (1 << CMARK_NODE_HTML_INLINE) | (1 << CMARK_NODE_EMPH) | 466 (1 << CMARK_NODE_STRONG) | (1 << CMARK_NODE_LINK) | 467 (1 << CMARK_NODE_IMAGE); 468 469 test_content(runner, CMARK_NODE_DOCUMENT, top_level_blocks); 470 test_content(runner, CMARK_NODE_BLOCK_QUOTE, top_level_blocks); 471 test_content(runner, CMARK_NODE_LIST, list_item_flag); 472 test_content(runner, CMARK_NODE_ITEM, top_level_blocks); 473 test_content(runner, CMARK_NODE_CODE_BLOCK, 0); 474 test_content(runner, CMARK_NODE_HTML_BLOCK, 0); 475 test_content(runner, CMARK_NODE_PARAGRAPH, all_inlines); 476 test_content(runner, CMARK_NODE_HEADING, all_inlines); 477 test_content(runner, CMARK_NODE_THEMATIC_BREAK, 0); 478 test_content(runner, CMARK_NODE_TEXT, 0); 479 test_content(runner, CMARK_NODE_SOFTBREAK, 0); 480 test_content(runner, CMARK_NODE_LINEBREAK, 0); 481 test_content(runner, CMARK_NODE_CODE, 0); 482 test_content(runner, CMARK_NODE_HTML_INLINE, 0); 483 test_content(runner, CMARK_NODE_EMPH, all_inlines); 484 test_content(runner, CMARK_NODE_STRONG, all_inlines); 485 test_content(runner, CMARK_NODE_LINK, all_inlines); 486 test_content(runner, CMARK_NODE_IMAGE, all_inlines); 487 } 488 489 static void test_content(test_batch_runner *runner, cmark_node_type type, 490 int allowed_content) { 491 cmark_node *node = cmark_node_new(type); 492 493 for (int i = 0; i < num_node_types; ++i) { 494 cmark_node_type child_type = node_types[i]; 495 cmark_node *child = cmark_node_new(child_type); 496 497 int got = cmark_node_append_child(node, child); 498 int expected = (allowed_content >> child_type) & 1; 499 500 INT_EQ(runner, got, expected, "add %d as child of %d", child_type, type); 501 502 cmark_node_free(child); 503 } 504 505 cmark_node_free(node); 506 } 507 508 static void parser(test_batch_runner *runner) { 509 test_md_to_html(runner, "No newline", "<p>No newline</p>\n", 510 "document without trailing newline"); 511 } 512 513 static void render_html(test_batch_runner *runner) { 514 char *html; 515 516 static const char markdown[] = "foo *bar*\n" 517 "\n" 518 "paragraph 2\n"; 519 cmark_node *doc = 520 cmark_parse_document(markdown, sizeof(markdown) - 1, CMARK_OPT_DEFAULT); 521 522 cmark_node *paragraph = cmark_node_first_child(doc); 523 html = cmark_render_html(paragraph, CMARK_OPT_DEFAULT); 524 STR_EQ(runner, html, "<p>foo <em>bar</em></p>\n", "render single paragraph"); 525 free(html); 526 527 cmark_node *string = cmark_node_first_child(paragraph); 528 html = cmark_render_html(string, CMARK_OPT_DEFAULT); 529 STR_EQ(runner, html, "foo ", "render single inline"); 530 free(html); 531 532 cmark_node *emph = cmark_node_next(string); 533 html = cmark_render_html(emph, CMARK_OPT_DEFAULT); 534 STR_EQ(runner, html, "<em>bar</em>", "render inline with children"); 535 free(html); 536 537 cmark_node_free(doc); 538 } 539 540 static void render_xml(test_batch_runner *runner) { 541 char *xml; 542 543 static const char markdown[] = "foo *bar*\n" 544 "\n" 545 "control -\x0C-\n" 546 "fffe -\xEF\xBF\xBE-\n" 547 "ffff -\xEF\xBF\xBF-\n" 548 "escape <>&\"\n" 549 "\n" 550 "```\ncode\n```\n"; 551 cmark_node *doc = 552 cmark_parse_document(markdown, sizeof(markdown) - 1, CMARK_OPT_DEFAULT); 553 554 xml = cmark_render_xml(doc, CMARK_OPT_DEFAULT); 555 STR_EQ(runner, xml, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" 556 "<!DOCTYPE document SYSTEM \"CommonMark.dtd\">\n" 557 "<document xmlns=\"http://commonmark.org/xml/1.0\">\n" 558 " <paragraph>\n" 559 " <text xml:space=\"preserve\">foo </text>\n" 560 " <emph>\n" 561 " <text xml:space=\"preserve\">bar</text>\n" 562 " </emph>\n" 563 " </paragraph>\n" 564 " <paragraph>\n" 565 " <text xml:space=\"preserve\">control -" UTF8_REPL "-</text>\n" 566 " <softbreak />\n" 567 " <text xml:space=\"preserve\">fffe -" UTF8_REPL "-</text>\n" 568 " <softbreak />\n" 569 " <text xml:space=\"preserve\">ffff -" UTF8_REPL "-</text>\n" 570 " <softbreak />\n" 571 " <text xml:space=\"preserve\">escape <>&"</text>\n" 572 " </paragraph>\n" 573 " <code_block xml:space=\"preserve\">code\n" 574 "</code_block>\n" 575 "</document>\n", 576 "render document"); 577 free(xml); 578 cmark_node *paragraph = cmark_node_first_child(doc); 579 xml = cmark_render_xml(paragraph, CMARK_OPT_DEFAULT | CMARK_OPT_SOURCEPOS); 580 STR_EQ(runner, xml, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" 581 "<!DOCTYPE document SYSTEM \"CommonMark.dtd\">\n" 582 "<paragraph sourcepos=\"1:1-1:9\">\n" 583 " <text sourcepos=\"1:1-1:4\" xml:space=\"preserve\">foo </text>\n" 584 " <emph sourcepos=\"1:5-1:9\">\n" 585 " <text sourcepos=\"1:6-1:8\" xml:space=\"preserve\">bar</text>\n" 586 " </emph>\n" 587 "</paragraph>\n", 588 "render first paragraph with source pos"); 589 free(xml); 590 cmark_node_free(doc); 591 } 592 593 static void render_man(test_batch_runner *runner) { 594 char *man; 595 596 static const char markdown[] = "foo *bar*\n" 597 "\n" 598 "- Lorem ipsum dolor sit amet,\n" 599 " consectetur adipiscing elit,\n" 600 "- sed do eiusmod tempor incididunt\n" 601 " ut labore et dolore magna aliqua.\n"; 602 cmark_node *doc = 603 cmark_parse_document(markdown, sizeof(markdown) - 1, CMARK_OPT_DEFAULT); 604 605 man = cmark_render_man(doc, CMARK_OPT_DEFAULT, 20); 606 STR_EQ(runner, man, ".PP\n" 607 "foo \\f[I]bar\\f[]\n" 608 ".IP \\[bu] 2\n" 609 "Lorem ipsum dolor\n" 610 "sit amet,\n" 611 "consectetur\n" 612 "adipiscing elit,\n" 613 ".IP \\[bu] 2\n" 614 "sed do eiusmod\n" 615 "tempor incididunt ut\n" 616 "labore et dolore\n" 617 "magna aliqua.\n", 618 "render document with wrapping"); 619 free(man); 620 man = cmark_render_man(doc, CMARK_OPT_DEFAULT, 0); 621 STR_EQ(runner, man, ".PP\n" 622 "foo \\f[I]bar\\f[]\n" 623 ".IP \\[bu] 2\n" 624 "Lorem ipsum dolor sit amet,\n" 625 "consectetur adipiscing elit,\n" 626 ".IP \\[bu] 2\n" 627 "sed do eiusmod tempor incididunt\n" 628 "ut labore et dolore magna aliqua.\n", 629 "render document without wrapping"); 630 free(man); 631 cmark_node_free(doc); 632 } 633 634 static void render_latex(test_batch_runner *runner) { 635 char *latex; 636 637 static const char markdown[] = "foo *bar* $%\n" 638 "\n" 639 "- Lorem ipsum dolor sit amet,\n" 640 " consectetur adipiscing elit,\n" 641 "- sed do eiusmod tempor incididunt\n" 642 " ut labore et dolore magna aliqua.\n"; 643 cmark_node *doc = 644 cmark_parse_document(markdown, sizeof(markdown) - 1, CMARK_OPT_DEFAULT); 645 646 latex = cmark_render_latex(doc, CMARK_OPT_DEFAULT, 20); 647 STR_EQ(runner, latex, "foo \\emph{bar} \\$\\%\n" 648 "\n" 649 "\\begin{itemize}\n" 650 "\\item Lorem ipsum\n" 651 "dolor sit amet,\n" 652 "consectetur\n" 653 "adipiscing elit,\n" 654 "\n" 655 "\\item sed do eiusmod\n" 656 "tempor incididunt ut\n" 657 "labore et dolore\n" 658 "magna aliqua.\n" 659 "\n" 660 "\\end{itemize}\n", 661 "render document with wrapping"); 662 free(latex); 663 latex = cmark_render_latex(doc, CMARK_OPT_DEFAULT, 0); 664 STR_EQ(runner, latex, "foo \\emph{bar} \\$\\%\n" 665 "\n" 666 "\\begin{itemize}\n" 667 "\\item Lorem ipsum dolor sit amet,\n" 668 "consectetur adipiscing elit,\n" 669 "\n" 670 "\\item sed do eiusmod tempor incididunt\n" 671 "ut labore et dolore magna aliqua.\n" 672 "\n" 673 "\\end{itemize}\n", 674 "render document without wrapping"); 675 free(latex); 676 cmark_node_free(doc); 677 } 678 679 static void render_commonmark(test_batch_runner *runner) { 680 char *commonmark; 681 682 static const char markdown[] = "> \\- foo *bar* \\*bar\\*\n" 683 "\n" 684 "- Lorem ipsum dolor sit amet,\n" 685 " consectetur adipiscing elit,\n" 686 "- sed do eiusmod tempor incididunt\n" 687 " ut labore et dolore magna aliqua.\n"; 688 cmark_node *doc = 689 cmark_parse_document(markdown, sizeof(markdown) - 1, CMARK_OPT_DEFAULT); 690 691 commonmark = cmark_render_commonmark(doc, CMARK_OPT_DEFAULT, 26); 692 STR_EQ(runner, commonmark, "> \\- foo *bar* \\*bar\\*\n" 693 "\n" 694 " - Lorem ipsum dolor sit\n" 695 " amet, consectetur\n" 696 " adipiscing elit,\n" 697 " - sed do eiusmod tempor\n" 698 " incididunt ut labore\n" 699 " et dolore magna\n" 700 " aliqua.\n", 701 "render document with wrapping"); 702 free(commonmark); 703 commonmark = cmark_render_commonmark(doc, CMARK_OPT_DEFAULT, 0); 704 STR_EQ(runner, commonmark, "> \\- foo *bar* \\*bar\\*\n" 705 "\n" 706 " - Lorem ipsum dolor sit amet,\n" 707 " consectetur adipiscing elit,\n" 708 " - sed do eiusmod tempor incididunt\n" 709 " ut labore et dolore magna aliqua.\n", 710 "render document without wrapping"); 711 free(commonmark); 712 713 cmark_node *text = cmark_node_new(CMARK_NODE_TEXT); 714 cmark_node_set_literal(text, "Hi"); 715 commonmark = cmark_render_commonmark(text, CMARK_OPT_DEFAULT, 0); 716 STR_EQ(runner, commonmark, "Hi\n", "render single inline node"); 717 free(commonmark); 718 719 cmark_node_free(text); 720 cmark_node_free(doc); 721 } 722 723 static void utf8(test_batch_runner *runner) { 724 // Ranges 725 test_char(runner, 1, "\x01", "valid utf8 01"); 726 test_char(runner, 1, "\x7F", "valid utf8 7F"); 727 test_char(runner, 0, "\x80", "invalid utf8 80"); 728 test_char(runner, 0, "\xBF", "invalid utf8 BF"); 729 test_char(runner, 0, "\xC0\x80", "invalid utf8 C080"); 730 test_char(runner, 0, "\xC1\xBF", "invalid utf8 C1BF"); 731 test_char(runner, 1, "\xC2\x80", "valid utf8 C280"); 732 test_char(runner, 1, "\xDF\xBF", "valid utf8 DFBF"); 733 test_char(runner, 0, "\xE0\x80\x80", "invalid utf8 E08080"); 734 test_char(runner, 0, "\xE0\x9F\xBF", "invalid utf8 E09FBF"); 735 test_char(runner, 1, "\xE0\xA0\x80", "valid utf8 E0A080"); 736 test_char(runner, 1, "\xED\x9F\xBF", "valid utf8 ED9FBF"); 737 test_char(runner, 0, "\xED\xA0\x80", "invalid utf8 EDA080"); 738 test_char(runner, 0, "\xED\xBF\xBF", "invalid utf8 EDBFBF"); 739 test_char(runner, 0, "\xF0\x80\x80\x80", "invalid utf8 F0808080"); 740 test_char(runner, 0, "\xF0\x8F\xBF\xBF", "invalid utf8 F08FBFBF"); 741 test_char(runner, 1, "\xF0\x90\x80\x80", "valid utf8 F0908080"); 742 test_char(runner, 1, "\xF4\x8F\xBF\xBF", "valid utf8 F48FBFBF"); 743 test_char(runner, 0, "\xF4\x90\x80\x80", "invalid utf8 F4908080"); 744 test_char(runner, 0, "\xF7\xBF\xBF\xBF", "invalid utf8 F7BFBFBF"); 745 test_char(runner, 0, "\xF8", "invalid utf8 F8"); 746 test_char(runner, 0, "\xFF", "invalid utf8 FF"); 747 748 // Incomplete byte sequences at end of input 749 test_incomplete_char(runner, "\xE0\xA0", "invalid utf8 E0A0"); 750 test_incomplete_char(runner, "\xF0\x90\x80", "invalid utf8 F09080"); 751 752 // Invalid continuation bytes 753 test_continuation_byte(runner, "\xC2\x80"); 754 test_continuation_byte(runner, "\xE0\xA0\x80"); 755 test_continuation_byte(runner, "\xF0\x90\x80\x80"); 756 757 // Test string containing null character 758 static const char string_with_null[] = "((((\0))))"; 759 char *html = cmark_markdown_to_html( 760 string_with_null, sizeof(string_with_null) - 1, CMARK_OPT_DEFAULT); 761 STR_EQ(runner, html, "<p>((((" UTF8_REPL "))))</p>\n", "utf8 with U+0000"); 762 free(html); 763 764 // Test NUL followed by newline 765 static const char string_with_nul_lf[] = "```\n\0\n```\n"; 766 html = cmark_markdown_to_html( 767 string_with_nul_lf, sizeof(string_with_nul_lf) - 1, CMARK_OPT_DEFAULT); 768 STR_EQ(runner, html, "<pre><code>\xef\xbf\xbd\n</code></pre>\n", 769 "utf8 with \\0\\n"); 770 free(html); 771 } 772 773 static void test_char(test_batch_runner *runner, int valid, const char *utf8, 774 const char *msg) { 775 char buf[20]; 776 sprintf(buf, "((((%s))))", utf8); 777 778 if (valid) { 779 char expected[30]; 780 sprintf(expected, "<p>((((%s))))</p>\n", utf8); 781 test_md_to_html(runner, buf, expected, msg); 782 } else { 783 test_md_to_html(runner, buf, "<p>((((" UTF8_REPL "))))</p>\n", msg); 784 } 785 } 786 787 static void test_incomplete_char(test_batch_runner *runner, const char *utf8, 788 const char *msg) { 789 char buf[20]; 790 sprintf(buf, "----%s", utf8); 791 test_md_to_html(runner, buf, "<p>----" UTF8_REPL "</p>\n", msg); 792 } 793 794 static void test_continuation_byte(test_batch_runner *runner, 795 const char *utf8) { 796 size_t len = strlen(utf8); 797 798 for (size_t pos = 1; pos < len; ++pos) { 799 char buf[20]; 800 sprintf(buf, "((((%s))))", utf8); 801 buf[4 + pos] = '\x20'; 802 803 char expected[50]; 804 strcpy(expected, "<p>((((" UTF8_REPL "\x20"); 805 for (size_t i = pos + 1; i < len; ++i) { 806 strcat(expected, UTF8_REPL); 807 } 808 strcat(expected, "))))</p>\n"); 809 810 char *html = 811 cmark_markdown_to_html(buf, strlen(buf), CMARK_OPT_VALIDATE_UTF8); 812 STR_EQ(runner, html, expected, "invalid utf8 continuation byte %d/%d", pos, 813 len); 814 free(html); 815 } 816 } 817 818 static void line_endings(test_batch_runner *runner) { 819 // Test list with different line endings 820 static const char list_with_endings[] = "- a\n- b\r\n- c\r- d"; 821 char *html = cmark_markdown_to_html( 822 list_with_endings, sizeof(list_with_endings) - 1, CMARK_OPT_DEFAULT); 823 STR_EQ(runner, html, 824 "<ul>\n<li>a</li>\n<li>b</li>\n<li>c</li>\n<li>d</li>\n</ul>\n", 825 "list with different line endings"); 826 free(html); 827 828 static const char crlf_lines[] = "line\r\nline\r\n"; 829 html = cmark_markdown_to_html(crlf_lines, sizeof(crlf_lines) - 1, 830 CMARK_OPT_DEFAULT | CMARK_OPT_HARDBREAKS); 831 STR_EQ(runner, html, "<p>line<br />\nline</p>\n", 832 "crlf endings with CMARK_OPT_HARDBREAKS"); 833 free(html); 834 html = cmark_markdown_to_html(crlf_lines, sizeof(crlf_lines) - 1, 835 CMARK_OPT_DEFAULT | CMARK_OPT_NOBREAKS); 836 STR_EQ(runner, html, "<p>line line</p>\n", 837 "crlf endings with CMARK_OPT_NOBREAKS"); 838 free(html); 839 840 static const char no_line_ending[] = "```\nline\n```"; 841 html = cmark_markdown_to_html(no_line_ending, sizeof(no_line_ending) - 1, 842 CMARK_OPT_DEFAULT); 843 STR_EQ(runner, html, "<pre><code>line\n</code></pre>\n", 844 "fenced code block with no final newline"); 845 free(html); 846 } 847 848 static void numeric_entities(test_batch_runner *runner) { 849 test_md_to_html(runner, "�", "<p>" UTF8_REPL "</p>\n", 850 "Invalid numeric entity 0"); 851 test_md_to_html(runner, "퟿", "<p>\xED\x9F\xBF</p>\n", 852 "Valid numeric entity 0xD7FF"); 853 test_md_to_html(runner, "�", "<p>" UTF8_REPL "</p>\n", 854 "Invalid numeric entity 0xD800"); 855 test_md_to_html(runner, "�", "<p>" UTF8_REPL "</p>\n", 856 "Invalid numeric entity 0xDFFF"); 857 test_md_to_html(runner, "", "<p>\xEE\x80\x80</p>\n", 858 "Valid numeric entity 0xE000"); 859 test_md_to_html(runner, "", "<p>\xF4\x8F\xBF\xBF</p>\n", 860 "Valid numeric entity 0x10FFFF"); 861 test_md_to_html(runner, "�", "<p>" UTF8_REPL "</p>\n", 862 "Invalid numeric entity 0x110000"); 863 test_md_to_html(runner, "�", "<p>&#x80000000;</p>\n", 864 "Invalid numeric entity 0x80000000"); 865 test_md_to_html(runner, "�", "<p>&#xFFFFFFFF;</p>\n", 866 "Invalid numeric entity 0xFFFFFFFF"); 867 test_md_to_html(runner, "�", "<p>&#99999999;</p>\n", 868 "Invalid numeric entity 99999999"); 869 870 test_md_to_html(runner, "&#;", "<p>&#;</p>\n", 871 "Min decimal entity length"); 872 test_md_to_html(runner, "&#x;", "<p>&#x;</p>\n", 873 "Min hexadecimal entity length"); 874 test_md_to_html(runner, "�", "<p>&#999999999;</p>\n", 875 "Max decimal entity length"); 876 test_md_to_html(runner, "A", "<p>&#x000000041;</p>\n", 877 "Max hexadecimal entity length"); 878 } 879 880 static void test_safe(test_batch_runner *runner) { 881 // Test safe mode 882 static const char raw_html[] = "<div>\nhi\n</div>\n\n<a>hi</" 883 "a>\n[link](JAVAscript:alert('hi'))\n![image](" 884 "file:my.js)\n"; 885 char *html = cmark_markdown_to_html(raw_html, sizeof(raw_html) - 1, 886 CMARK_OPT_DEFAULT); 887 STR_EQ(runner, html, "<!-- raw HTML omitted -->\n<p><!-- raw HTML omitted " 888 "-->hi<!-- raw HTML omitted -->\n<a " 889 "href=\"\">link</a>\n<img src=\"\" alt=\"image\" " 890 "/></p>\n", 891 "input with raw HTML and dangerous links"); 892 free(html); 893 } 894 895 static void test_md_to_html(test_batch_runner *runner, const char *markdown, 896 const char *expected_html, const char *msg) { 897 char *html = cmark_markdown_to_html(markdown, strlen(markdown), 898 CMARK_OPT_VALIDATE_UTF8); 899 STR_EQ(runner, html, expected_html, msg); 900 free(html); 901 } 902 903 static void test_feed_across_line_ending(test_batch_runner *runner) { 904 // See #117 905 cmark_parser *parser = cmark_parser_new(CMARK_OPT_DEFAULT); 906 cmark_parser_feed(parser, "line1\r", 6); 907 cmark_parser_feed(parser, "\nline2\r\n", 8); 908 cmark_node *document = cmark_parser_finish(parser); 909 OK(runner, document->first_child->next == NULL, "document has one paragraph"); 910 cmark_parser_free(parser); 911 cmark_node_free(document); 912 } 913 914 static void source_pos(test_batch_runner *runner) { 915 static const char markdown[] = 916 "# Hi *there*.\n" 917 "\n" 918 "Hello “ <http://www.google.com>\n" 919 "there `hi` -- [okay](www.google.com (ok)).\n" 920 "\n" 921 "> 1. Okay.\n" 922 "> Sure.\n" 923 ">\n" 924 "> 2. Yes, okay.\n" 925 "> ![ok](hi \"yes\")\n"; 926 927 cmark_node *doc = cmark_parse_document(markdown, sizeof(markdown) - 1, CMARK_OPT_DEFAULT); 928 char *xml = cmark_render_xml(doc, CMARK_OPT_DEFAULT | CMARK_OPT_SOURCEPOS); 929 STR_EQ(runner, xml, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" 930 "<!DOCTYPE document SYSTEM \"CommonMark.dtd\">\n" 931 "<document sourcepos=\"1:1-10:20\" xmlns=\"http://commonmark.org/xml/1.0\">\n" 932 " <heading sourcepos=\"1:1-1:13\" level=\"1\">\n" 933 " <text sourcepos=\"1:3-1:5\" xml:space=\"preserve\">Hi </text>\n" 934 " <emph sourcepos=\"1:6-1:12\">\n" 935 " <text sourcepos=\"1:7-1:11\" xml:space=\"preserve\">there</text>\n" 936 " </emph>\n" 937 " <text sourcepos=\"1:13-1:13\" xml:space=\"preserve\">.</text>\n" 938 " </heading>\n" 939 " <paragraph sourcepos=\"3:1-4:42\">\n" 940 " <text sourcepos=\"3:1-3:14\" xml:space=\"preserve\">Hello “ </text>\n" 941 " <link sourcepos=\"3:15-3:37\" destination=\"http://www.google.com\">\n" 942 " <text sourcepos=\"3:16-3:36\" xml:space=\"preserve\">http://www.google.com</text>\n" 943 " </link>\n" 944 " <softbreak />\n" 945 " <text sourcepos=\"4:1-4:6\" xml:space=\"preserve\">there </text>\n" 946 " <code sourcepos=\"4:8-4:9\" xml:space=\"preserve\">hi</code>\n" 947 " <text sourcepos=\"4:11-4:14\" xml:space=\"preserve\"> -- </text>\n" 948 " <link sourcepos=\"4:15-4:41\" destination=\"www.google.com\" title=\"ok\">\n" 949 " <text sourcepos=\"4:16-4:19\" xml:space=\"preserve\">okay</text>\n" 950 " </link>\n" 951 " <text sourcepos=\"4:42-4:42\" xml:space=\"preserve\">.</text>\n" 952 " </paragraph>\n" 953 " <block_quote sourcepos=\"6:1-10:20\">\n" 954 " <list sourcepos=\"6:3-10:20\" type=\"ordered\" start=\"1\" delim=\"period\" tight=\"false\">\n" 955 " <item sourcepos=\"6:3-8:1\">\n" 956 " <paragraph sourcepos=\"6:6-7:10\">\n" 957 " <text sourcepos=\"6:6-6:10\" xml:space=\"preserve\">Okay.</text>\n" 958 " <softbreak />\n" 959 " <text sourcepos=\"7:6-7:10\" xml:space=\"preserve\">Sure.</text>\n" 960 " </paragraph>\n" 961 " </item>\n" 962 " <item sourcepos=\"9:3-10:20\">\n" 963 " <paragraph sourcepos=\"9:6-10:20\">\n" 964 " <text sourcepos=\"9:6-9:15\" xml:space=\"preserve\">Yes, okay.</text>\n" 965 " <softbreak />\n" 966 " <image sourcepos=\"10:6-10:20\" destination=\"hi\" title=\"yes\">\n" 967 " <text sourcepos=\"10:8-10:9\" xml:space=\"preserve\">ok</text>\n" 968 " </image>\n" 969 " </paragraph>\n" 970 " </item>\n" 971 " </list>\n" 972 " </block_quote>\n" 973 "</document>\n", 974 "sourcepos are as expected"); 975 free(xml); 976 cmark_node_free(doc); 977 } 978 979 static void source_pos_inlines(test_batch_runner *runner) { 980 { 981 static const char markdown[] = 982 "*first*\n" 983 "second\n"; 984 985 cmark_node *doc = cmark_parse_document(markdown, sizeof(markdown) - 1, CMARK_OPT_DEFAULT); 986 char *xml = cmark_render_xml(doc, CMARK_OPT_DEFAULT | CMARK_OPT_SOURCEPOS); 987 STR_EQ(runner, xml, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" 988 "<!DOCTYPE document SYSTEM \"CommonMark.dtd\">\n" 989 "<document sourcepos=\"1:1-2:6\" xmlns=\"http://commonmark.org/xml/1.0\">\n" 990 " <paragraph sourcepos=\"1:1-2:6\">\n" 991 " <emph sourcepos=\"1:1-1:7\">\n" 992 " <text sourcepos=\"1:2-1:6\" xml:space=\"preserve\">first</text>\n" 993 " </emph>\n" 994 " <softbreak />\n" 995 " <text sourcepos=\"2:1-2:6\" xml:space=\"preserve\">second</text>\n" 996 " </paragraph>\n" 997 "</document>\n", 998 "sourcepos are as expected"); 999 free(xml); 1000 cmark_node_free(doc); 1001 } 1002 { 1003 static const char markdown[] = 1004 "*first\n" 1005 "second*\n"; 1006 1007 cmark_node *doc = cmark_parse_document(markdown, sizeof(markdown) - 1, CMARK_OPT_DEFAULT); 1008 char *xml = cmark_render_xml(doc, CMARK_OPT_DEFAULT | CMARK_OPT_SOURCEPOS); 1009 STR_EQ(runner, xml, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" 1010 "<!DOCTYPE document SYSTEM \"CommonMark.dtd\">\n" 1011 "<document sourcepos=\"1:1-2:7\" xmlns=\"http://commonmark.org/xml/1.0\">\n" 1012 " <paragraph sourcepos=\"1:1-2:7\">\n" 1013 " <emph sourcepos=\"1:1-2:7\">\n" 1014 " <text sourcepos=\"1:2-1:6\" xml:space=\"preserve\">first</text>\n" 1015 " <softbreak />\n" 1016 " <text sourcepos=\"2:1-2:6\" xml:space=\"preserve\">second</text>\n" 1017 " </emph>\n" 1018 " </paragraph>\n" 1019 "</document>\n", 1020 "sourcepos are as expected"); 1021 free(xml); 1022 cmark_node_free(doc); 1023 } 1024 } 1025 1026 static void ref_source_pos(test_batch_runner *runner) { 1027 static const char markdown[] = 1028 "Let's try [reference] links.\n" 1029 "\n" 1030 "[reference]: https://github.com (GitHub)\n"; 1031 1032 cmark_node *doc = cmark_parse_document(markdown, sizeof(markdown) - 1, CMARK_OPT_DEFAULT); 1033 char *xml = cmark_render_xml(doc, CMARK_OPT_DEFAULT | CMARK_OPT_SOURCEPOS); 1034 STR_EQ(runner, xml, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" 1035 "<!DOCTYPE document SYSTEM \"CommonMark.dtd\">\n" 1036 "<document sourcepos=\"1:1-3:40\" xmlns=\"http://commonmark.org/xml/1.0\">\n" 1037 " <paragraph sourcepos=\"1:1-1:28\">\n" 1038 " <text sourcepos=\"1:1-1:10\" xml:space=\"preserve\">Let's try </text>\n" 1039 " <link sourcepos=\"1:11-1:21\" destination=\"https://github.com\" title=\"GitHub\">\n" 1040 " <text sourcepos=\"1:12-1:20\" xml:space=\"preserve\">reference</text>\n" 1041 " </link>\n" 1042 " <text sourcepos=\"1:22-1:28\" xml:space=\"preserve\"> links.</text>\n" 1043 " </paragraph>\n" 1044 "</document>\n", 1045 "sourcepos are as expected"); 1046 free(xml); 1047 cmark_node_free(doc); 1048 } 1049 1050 int main() { 1051 int retval; 1052 test_batch_runner *runner = test_batch_runner_new(); 1053 1054 version(runner); 1055 constructor(runner); 1056 accessors(runner); 1057 free_parent(runner); 1058 node_check(runner); 1059 iterator(runner); 1060 iterator_delete(runner); 1061 create_tree(runner); 1062 custom_nodes(runner); 1063 hierarchy(runner); 1064 parser(runner); 1065 render_html(runner); 1066 render_xml(runner); 1067 render_man(runner); 1068 render_latex(runner); 1069 render_commonmark(runner); 1070 utf8(runner); 1071 line_endings(runner); 1072 numeric_entities(runner); 1073 test_cplusplus(runner); 1074 test_safe(runner); 1075 test_feed_across_line_ending(runner); 1076 source_pos(runner); 1077 source_pos_inlines(runner); 1078 ref_source_pos(runner); 1079 1080 test_print_summary(runner); 1081 retval = test_ok(runner) ? 0 : 1; 1082 free(runner); 1083 1084 return retval; 1085 }