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 &lt;&gt;&amp;&quot;</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, "&#0;", "<p>" UTF8_REPL "</p>\n",
 850                   "Invalid numeric entity 0");
 851   test_md_to_html(runner, "&#55295;", "<p>\xED\x9F\xBF</p>\n",
 852                   "Valid numeric entity 0xD7FF");
 853   test_md_to_html(runner, "&#xD800;", "<p>" UTF8_REPL "</p>\n",
 854                   "Invalid numeric entity 0xD800");
 855   test_md_to_html(runner, "&#xDFFF;", "<p>" UTF8_REPL "</p>\n",
 856                   "Invalid numeric entity 0xDFFF");
 857   test_md_to_html(runner, "&#57344;", "<p>\xEE\x80\x80</p>\n",
 858                   "Valid numeric entity 0xE000");
 859   test_md_to_html(runner, "&#x10FFFF;", "<p>\xF4\x8F\xBF\xBF</p>\n",
 860                   "Valid numeric entity 0x10FFFF");
 861   test_md_to_html(runner, "&#x110000;", "<p>" UTF8_REPL "</p>\n",
 862                   "Invalid numeric entity 0x110000");
 863   test_md_to_html(runner, "&#x80000000;", "<p>&amp;#x80000000;</p>\n",
 864                   "Invalid numeric entity 0x80000000");
 865   test_md_to_html(runner, "&#xFFFFFFFF;", "<p>&amp;#xFFFFFFFF;</p>\n",
 866                   "Invalid numeric entity 0xFFFFFFFF");
 867   test_md_to_html(runner, "&#99999999;", "<p>&amp;#99999999;</p>\n",
 868                   "Invalid numeric entity 99999999");
 869 
 870   test_md_to_html(runner, "&#;", "<p>&amp;#;</p>\n",
 871                   "Min decimal entity length");
 872   test_md_to_html(runner, "&#x;", "<p>&amp;#x;</p>\n",
 873                   "Min hexadecimal entity length");
 874   test_md_to_html(runner, "&#999999999;", "<p>&amp;#999999999;</p>\n",
 875                   "Max decimal entity length");
 876   test_md_to_html(runner, "&#x000000041;", "<p>&amp;#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 &ldquo; <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 }