1 #include <stdlib.h>
2 #include <string.h>
3 
4 #include "config.h"
5 #include "node.h"
6 
7 static void S_node_unlink(cmark_node *node);
8 
9 #define NODE_MEM(node) cmark_node_mem(node)
10 
S_is_block(cmark_node * node)11 static CMARK_INLINE bool S_is_block(cmark_node *node) {
12   if (node == NULL) {
13     return false;
14   }
15   return node->type >= CMARK_NODE_FIRST_BLOCK &&
16          node->type <= CMARK_NODE_LAST_BLOCK;
17 }
18 
S_is_inline(cmark_node * node)19 static CMARK_INLINE bool S_is_inline(cmark_node *node) {
20   if (node == NULL) {
21     return false;
22   }
23   return node->type >= CMARK_NODE_FIRST_INLINE &&
24          node->type <= CMARK_NODE_LAST_INLINE;
25 }
26 
S_can_contain(cmark_node * node,cmark_node * child)27 static bool S_can_contain(cmark_node *node, cmark_node *child) {
28   cmark_node *cur;
29 
30   if (node == NULL || child == NULL) {
31     return false;
32   }
33 
34   // Verify that child is not an ancestor of node or equal to node.
35   cur = node;
36   do {
37     if (cur == child) {
38       return false;
39     }
40     cur = cur->parent;
41   } while (cur != NULL);
42 
43   if (child->type == CMARK_NODE_DOCUMENT) {
44     return false;
45   }
46 
47   switch (node->type) {
48   case CMARK_NODE_DOCUMENT:
49   case CMARK_NODE_BLOCK_QUOTE:
50   case CMARK_NODE_ITEM:
51     return S_is_block(child) && child->type != CMARK_NODE_ITEM;
52 
53   case CMARK_NODE_LIST:
54     return child->type == CMARK_NODE_ITEM;
55 
56   case CMARK_NODE_CUSTOM_BLOCK:
57     return true;
58 
59   case CMARK_NODE_PARAGRAPH:
60   case CMARK_NODE_HEADING:
61   case CMARK_NODE_EMPH:
62   case CMARK_NODE_STRONG:
63   case CMARK_NODE_LINK:
64   case CMARK_NODE_IMAGE:
65   case CMARK_NODE_CUSTOM_INLINE:
66     return S_is_inline(child);
67 
68   default:
69     break;
70   }
71 
72   return false;
73 }
74 
cmark_node_new_with_mem(cmark_node_type type,cmark_mem * mem)75 cmark_node *cmark_node_new_with_mem(cmark_node_type type, cmark_mem *mem) {
76   cmark_node *node = (cmark_node *)mem->calloc(1, sizeof(*node));
77   cmark_strbuf_init(mem, &node->content, 0);
78   node->type = (uint16_t)type;
79 
80   switch (node->type) {
81   case CMARK_NODE_HEADING:
82     node->as.heading.level = 1;
83     break;
84 
85   case CMARK_NODE_LIST: {
86     cmark_list *list = &node->as.list;
87     list->list_type = CMARK_BULLET_LIST;
88     list->start = 0;
89     list->tight = false;
90     break;
91   }
92 
93   default:
94     break;
95   }
96 
97   return node;
98 }
99 
cmark_node_new(cmark_node_type type)100 cmark_node *cmark_node_new(cmark_node_type type) {
101   extern cmark_mem DEFAULT_MEM_ALLOCATOR;
102   return cmark_node_new_with_mem(type, &DEFAULT_MEM_ALLOCATOR);
103 }
104 
105 // Free a cmark_node list and any children.
S_free_nodes(cmark_node * e)106 static void S_free_nodes(cmark_node *e) {
107   cmark_node *next;
108   while (e != NULL) {
109     cmark_strbuf_free(&e->content);
110     switch (e->type) {
111     case CMARK_NODE_CODE_BLOCK:
112       cmark_chunk_free(NODE_MEM(e), &e->as.code.info);
113       cmark_chunk_free(NODE_MEM(e), &e->as.code.literal);
114       break;
115     case CMARK_NODE_TEXT:
116     case CMARK_NODE_HTML_INLINE:
117     case CMARK_NODE_CODE:
118     case CMARK_NODE_HTML_BLOCK:
119       cmark_chunk_free(NODE_MEM(e), &e->as.literal);
120       break;
121     case CMARK_NODE_LINK:
122     case CMARK_NODE_IMAGE:
123       cmark_chunk_free(NODE_MEM(e), &e->as.link.url);
124       cmark_chunk_free(NODE_MEM(e), &e->as.link.title);
125       break;
126     case CMARK_NODE_CUSTOM_BLOCK:
127     case CMARK_NODE_CUSTOM_INLINE:
128       cmark_chunk_free(NODE_MEM(e), &e->as.custom.on_enter);
129       cmark_chunk_free(NODE_MEM(e), &e->as.custom.on_exit);
130       break;
131     default:
132       break;
133     }
134     if (e->last_child) {
135       // Splice children into list
136       e->last_child->next = e->next;
137       e->next = e->first_child;
138     }
139     next = e->next;
140     NODE_MEM(e)->free(e);
141     e = next;
142   }
143 }
144 
cmark_node_free(cmark_node * node)145 void cmark_node_free(cmark_node *node) {
146   S_node_unlink(node);
147   node->next = NULL;
148   S_free_nodes(node);
149 }
150 
cmark_node_get_type(cmark_node * node)151 cmark_node_type cmark_node_get_type(cmark_node *node) {
152   if (node == NULL) {
153     return CMARK_NODE_NONE;
154   } else {
155     return (cmark_node_type)node->type;
156   }
157 }
158 
cmark_node_get_type_string(cmark_node * node)159 const char *cmark_node_get_type_string(cmark_node *node) {
160   if (node == NULL) {
161     return "NONE";
162   }
163 
164   switch (node->type) {
165   case CMARK_NODE_NONE:
166     return "none";
167   case CMARK_NODE_DOCUMENT:
168     return "document";
169   case CMARK_NODE_BLOCK_QUOTE:
170     return "block_quote";
171   case CMARK_NODE_LIST:
172     return "list";
173   case CMARK_NODE_ITEM:
174     return "item";
175   case CMARK_NODE_CODE_BLOCK:
176     return "code_block";
177   case CMARK_NODE_HTML_BLOCK:
178     return "html_block";
179   case CMARK_NODE_CUSTOM_BLOCK:
180     return "custom_block";
181   case CMARK_NODE_PARAGRAPH:
182     return "paragraph";
183   case CMARK_NODE_HEADING:
184     return "heading";
185   case CMARK_NODE_THEMATIC_BREAK:
186     return "thematic_break";
187   case CMARK_NODE_TEXT:
188     return "text";
189   case CMARK_NODE_SOFTBREAK:
190     return "softbreak";
191   case CMARK_NODE_LINEBREAK:
192     return "linebreak";
193   case CMARK_NODE_CODE:
194     return "code";
195   case CMARK_NODE_HTML_INLINE:
196     return "html_inline";
197   case CMARK_NODE_CUSTOM_INLINE:
198     return "custom_inline";
199   case CMARK_NODE_EMPH:
200     return "emph";
201   case CMARK_NODE_STRONG:
202     return "strong";
203   case CMARK_NODE_LINK:
204     return "link";
205   case CMARK_NODE_IMAGE:
206     return "image";
207   }
208 
209   return "<unknown>";
210 }
211 
cmark_node_next(cmark_node * node)212 cmark_node *cmark_node_next(cmark_node *node) {
213   if (node == NULL) {
214     return NULL;
215   } else {
216     return node->next;
217   }
218 }
219 
cmark_node_previous(cmark_node * node)220 cmark_node *cmark_node_previous(cmark_node *node) {
221   if (node == NULL) {
222     return NULL;
223   } else {
224     return node->prev;
225   }
226 }
227 
cmark_node_parent(cmark_node * node)228 cmark_node *cmark_node_parent(cmark_node *node) {
229   if (node == NULL) {
230     return NULL;
231   } else {
232     return node->parent;
233   }
234 }
235 
cmark_node_first_child(cmark_node * node)236 cmark_node *cmark_node_first_child(cmark_node *node) {
237   if (node == NULL) {
238     return NULL;
239   } else {
240     return node->first_child;
241   }
242 }
243 
cmark_node_last_child(cmark_node * node)244 cmark_node *cmark_node_last_child(cmark_node *node) {
245   if (node == NULL) {
246     return NULL;
247   } else {
248     return node->last_child;
249   }
250 }
251 
cmark_node_get_user_data(cmark_node * node)252 void *cmark_node_get_user_data(cmark_node *node) {
253   if (node == NULL) {
254     return NULL;
255   } else {
256     return node->user_data;
257   }
258 }
259 
cmark_node_set_user_data(cmark_node * node,void * user_data)260 int cmark_node_set_user_data(cmark_node *node, void *user_data) {
261   if (node == NULL) {
262     return 0;
263   }
264   node->user_data = user_data;
265   return 1;
266 }
267 
cmark_node_get_literal(cmark_node * node)268 const char *cmark_node_get_literal(cmark_node *node) {
269   if (node == NULL) {
270     return NULL;
271   }
272 
273   switch (node->type) {
274   case CMARK_NODE_HTML_BLOCK:
275   case CMARK_NODE_TEXT:
276   case CMARK_NODE_HTML_INLINE:
277   case CMARK_NODE_CODE:
278     return cmark_chunk_to_cstr(NODE_MEM(node), &node->as.literal);
279 
280   case CMARK_NODE_CODE_BLOCK:
281     return cmark_chunk_to_cstr(NODE_MEM(node), &node->as.code.literal);
282 
283   default:
284     break;
285   }
286 
287   return NULL;
288 }
289 
cmark_node_set_literal(cmark_node * node,const char * content)290 int cmark_node_set_literal(cmark_node *node, const char *content) {
291   if (node == NULL) {
292     return 0;
293   }
294 
295   switch (node->type) {
296   case CMARK_NODE_HTML_BLOCK:
297   case CMARK_NODE_TEXT:
298   case CMARK_NODE_HTML_INLINE:
299   case CMARK_NODE_CODE:
300     cmark_chunk_set_cstr(NODE_MEM(node), &node->as.literal, content);
301     return 1;
302 
303   case CMARK_NODE_CODE_BLOCK:
304     cmark_chunk_set_cstr(NODE_MEM(node), &node->as.code.literal, content);
305     return 1;
306 
307   default:
308     break;
309   }
310 
311   return 0;
312 }
313 
cmark_node_get_heading_level(cmark_node * node)314 int cmark_node_get_heading_level(cmark_node *node) {
315   if (node == NULL) {
316     return 0;
317   }
318 
319   switch (node->type) {
320   case CMARK_NODE_HEADING:
321     return node->as.heading.level;
322 
323   default:
324     break;
325   }
326 
327   return 0;
328 }
329 
cmark_node_set_heading_level(cmark_node * node,int level)330 int cmark_node_set_heading_level(cmark_node *node, int level) {
331   if (node == NULL || level < 1 || level > 6) {
332     return 0;
333   }
334 
335   switch (node->type) {
336   case CMARK_NODE_HEADING:
337     node->as.heading.level = level;
338     return 1;
339 
340   default:
341     break;
342   }
343 
344   return 0;
345 }
346 
cmark_node_get_list_type(cmark_node * node)347 cmark_list_type cmark_node_get_list_type(cmark_node *node) {
348   if (node == NULL) {
349     return CMARK_NO_LIST;
350   }
351 
352   if (node->type == CMARK_NODE_LIST) {
353     return node->as.list.list_type;
354   } else {
355     return CMARK_NO_LIST;
356   }
357 }
358 
cmark_node_set_list_type(cmark_node * node,cmark_list_type type)359 int cmark_node_set_list_type(cmark_node *node, cmark_list_type type) {
360   if (!(type == CMARK_BULLET_LIST || type == CMARK_ORDERED_LIST)) {
361     return 0;
362   }
363 
364   if (node == NULL) {
365     return 0;
366   }
367 
368   if (node->type == CMARK_NODE_LIST) {
369     node->as.list.list_type = type;
370     return 1;
371   } else {
372     return 0;
373   }
374 }
375 
cmark_node_get_list_delim(cmark_node * node)376 cmark_delim_type cmark_node_get_list_delim(cmark_node *node) {
377   if (node == NULL) {
378     return CMARK_NO_DELIM;
379   }
380 
381   if (node->type == CMARK_NODE_LIST) {
382     return node->as.list.delimiter;
383   } else {
384     return CMARK_NO_DELIM;
385   }
386 }
387 
cmark_node_set_list_delim(cmark_node * node,cmark_delim_type delim)388 int cmark_node_set_list_delim(cmark_node *node, cmark_delim_type delim) {
389   if (!(delim == CMARK_PERIOD_DELIM || delim == CMARK_PAREN_DELIM)) {
390     return 0;
391   }
392 
393   if (node == NULL) {
394     return 0;
395   }
396 
397   if (node->type == CMARK_NODE_LIST) {
398     node->as.list.delimiter = delim;
399     return 1;
400   } else {
401     return 0;
402   }
403 }
404 
cmark_node_get_list_start(cmark_node * node)405 int cmark_node_get_list_start(cmark_node *node) {
406   if (node == NULL) {
407     return 0;
408   }
409 
410   if (node->type == CMARK_NODE_LIST) {
411     return node->as.list.start;
412   } else {
413     return 0;
414   }
415 }
416 
cmark_node_set_list_start(cmark_node * node,int start)417 int cmark_node_set_list_start(cmark_node *node, int start) {
418   if (node == NULL || start < 0) {
419     return 0;
420   }
421 
422   if (node->type == CMARK_NODE_LIST) {
423     node->as.list.start = start;
424     return 1;
425   } else {
426     return 0;
427   }
428 }
429 
cmark_node_get_list_tight(cmark_node * node)430 int cmark_node_get_list_tight(cmark_node *node) {
431   if (node == NULL) {
432     return 0;
433   }
434 
435   if (node->type == CMARK_NODE_LIST) {
436     return node->as.list.tight;
437   } else {
438     return 0;
439   }
440 }
441 
cmark_node_set_list_tight(cmark_node * node,int tight)442 int cmark_node_set_list_tight(cmark_node *node, int tight) {
443   if (node == NULL) {
444     return 0;
445   }
446 
447   if (node->type == CMARK_NODE_LIST) {
448     node->as.list.tight = tight == 1;
449     return 1;
450   } else {
451     return 0;
452   }
453 }
454 
cmark_node_get_fence_info(cmark_node * node)455 const char *cmark_node_get_fence_info(cmark_node *node) {
456   if (node == NULL) {
457     return NULL;
458   }
459 
460   if (node->type == CMARK_NODE_CODE_BLOCK) {
461     return cmark_chunk_to_cstr(NODE_MEM(node), &node->as.code.info);
462   } else {
463     return NULL;
464   }
465 }
466 
cmark_node_set_fence_info(cmark_node * node,const char * info)467 int cmark_node_set_fence_info(cmark_node *node, const char *info) {
468   if (node == NULL) {
469     return 0;
470   }
471 
472   if (node->type == CMARK_NODE_CODE_BLOCK) {
473     cmark_chunk_set_cstr(NODE_MEM(node), &node->as.code.info, info);
474     return 1;
475   } else {
476     return 0;
477   }
478 }
479 
cmark_node_get_url(cmark_node * node)480 const char *cmark_node_get_url(cmark_node *node) {
481   if (node == NULL) {
482     return NULL;
483   }
484 
485   switch (node->type) {
486   case CMARK_NODE_LINK:
487   case CMARK_NODE_IMAGE:
488     return cmark_chunk_to_cstr(NODE_MEM(node), &node->as.link.url);
489   default:
490     break;
491   }
492 
493   return NULL;
494 }
495 
cmark_node_set_url(cmark_node * node,const char * url)496 int cmark_node_set_url(cmark_node *node, const char *url) {
497   if (node == NULL) {
498     return 0;
499   }
500 
501   switch (node->type) {
502   case CMARK_NODE_LINK:
503   case CMARK_NODE_IMAGE:
504     cmark_chunk_set_cstr(NODE_MEM(node), &node->as.link.url, url);
505     return 1;
506   default:
507     break;
508   }
509 
510   return 0;
511 }
512 
cmark_node_get_title(cmark_node * node)513 const char *cmark_node_get_title(cmark_node *node) {
514   if (node == NULL) {
515     return NULL;
516   }
517 
518   switch (node->type) {
519   case CMARK_NODE_LINK:
520   case CMARK_NODE_IMAGE:
521     return cmark_chunk_to_cstr(NODE_MEM(node), &node->as.link.title);
522   default:
523     break;
524   }
525 
526   return NULL;
527 }
528 
cmark_node_set_title(cmark_node * node,const char * title)529 int cmark_node_set_title(cmark_node *node, const char *title) {
530   if (node == NULL) {
531     return 0;
532   }
533 
534   switch (node->type) {
535   case CMARK_NODE_LINK:
536   case CMARK_NODE_IMAGE:
537     cmark_chunk_set_cstr(NODE_MEM(node), &node->as.link.title, title);
538     return 1;
539   default:
540     break;
541   }
542 
543   return 0;
544 }
545 
cmark_node_get_on_enter(cmark_node * node)546 const char *cmark_node_get_on_enter(cmark_node *node) {
547   if (node == NULL) {
548     return NULL;
549   }
550 
551   switch (node->type) {
552   case CMARK_NODE_CUSTOM_INLINE:
553   case CMARK_NODE_CUSTOM_BLOCK:
554     return cmark_chunk_to_cstr(NODE_MEM(node), &node->as.custom.on_enter);
555   default:
556     break;
557   }
558 
559   return NULL;
560 }
561 
cmark_node_set_on_enter(cmark_node * node,const char * on_enter)562 int cmark_node_set_on_enter(cmark_node *node, const char *on_enter) {
563   if (node == NULL) {
564     return 0;
565   }
566 
567   switch (node->type) {
568   case CMARK_NODE_CUSTOM_INLINE:
569   case CMARK_NODE_CUSTOM_BLOCK:
570     cmark_chunk_set_cstr(NODE_MEM(node), &node->as.custom.on_enter, on_enter);
571     return 1;
572   default:
573     break;
574   }
575 
576   return 0;
577 }
578 
cmark_node_get_on_exit(cmark_node * node)579 const char *cmark_node_get_on_exit(cmark_node *node) {
580   if (node == NULL) {
581     return NULL;
582   }
583 
584   switch (node->type) {
585   case CMARK_NODE_CUSTOM_INLINE:
586   case CMARK_NODE_CUSTOM_BLOCK:
587     return cmark_chunk_to_cstr(NODE_MEM(node), &node->as.custom.on_exit);
588   default:
589     break;
590   }
591 
592   return NULL;
593 }
594 
cmark_node_set_on_exit(cmark_node * node,const char * on_exit)595 int cmark_node_set_on_exit(cmark_node *node, const char *on_exit) {
596   if (node == NULL) {
597     return 0;
598   }
599 
600   switch (node->type) {
601   case CMARK_NODE_CUSTOM_INLINE:
602   case CMARK_NODE_CUSTOM_BLOCK:
603     cmark_chunk_set_cstr(NODE_MEM(node), &node->as.custom.on_exit, on_exit);
604     return 1;
605   default:
606     break;
607   }
608 
609   return 0;
610 }
611 
cmark_node_get_start_line(cmark_node * node)612 int cmark_node_get_start_line(cmark_node *node) {
613   if (node == NULL) {
614     return 0;
615   }
616   return node->start_line;
617 }
618 
cmark_node_get_start_column(cmark_node * node)619 int cmark_node_get_start_column(cmark_node *node) {
620   if (node == NULL) {
621     return 0;
622   }
623   return node->start_column;
624 }
625 
cmark_node_get_end_line(cmark_node * node)626 int cmark_node_get_end_line(cmark_node *node) {
627   if (node == NULL) {
628     return 0;
629   }
630   return node->end_line;
631 }
632 
cmark_node_get_end_column(cmark_node * node)633 int cmark_node_get_end_column(cmark_node *node) {
634   if (node == NULL) {
635     return 0;
636   }
637   return node->end_column;
638 }
639 
640 // Unlink a node without adjusting its next, prev, and parent pointers.
S_node_unlink(cmark_node * node)641 static void S_node_unlink(cmark_node *node) {
642   if (node == NULL) {
643     return;
644   }
645 
646   if (node->prev) {
647     node->prev->next = node->next;
648   }
649   if (node->next) {
650     node->next->prev = node->prev;
651   }
652 
653   // Adjust first_child and last_child of parent.
654   cmark_node *parent = node->parent;
655   if (parent) {
656     if (parent->first_child == node) {
657       parent->first_child = node->next;
658     }
659     if (parent->last_child == node) {
660       parent->last_child = node->prev;
661     }
662   }
663 }
664 
cmark_node_unlink(cmark_node * node)665 void cmark_node_unlink(cmark_node *node) {
666   S_node_unlink(node);
667 
668   node->next = NULL;
669   node->prev = NULL;
670   node->parent = NULL;
671 }
672 
cmark_node_insert_before(cmark_node * node,cmark_node * sibling)673 int cmark_node_insert_before(cmark_node *node, cmark_node *sibling) {
674   if (node == NULL || sibling == NULL) {
675     return 0;
676   }
677 
678   if (!node->parent || !S_can_contain(node->parent, sibling)) {
679     return 0;
680   }
681 
682   S_node_unlink(sibling);
683 
684   cmark_node *old_prev = node->prev;
685 
686   // Insert 'sibling' between 'old_prev' and 'node'.
687   if (old_prev) {
688     old_prev->next = sibling;
689   }
690   sibling->prev = old_prev;
691   sibling->next = node;
692   node->prev = sibling;
693 
694   // Set new parent.
695   cmark_node *parent = node->parent;
696   sibling->parent = parent;
697 
698   // Adjust first_child of parent if inserted as first child.
699   if (parent && !old_prev) {
700     parent->first_child = sibling;
701   }
702 
703   return 1;
704 }
705 
cmark_node_insert_after(cmark_node * node,cmark_node * sibling)706 int cmark_node_insert_after(cmark_node *node, cmark_node *sibling) {
707   if (node == NULL || sibling == NULL) {
708     return 0;
709   }
710 
711   if (!node->parent || !S_can_contain(node->parent, sibling)) {
712     return 0;
713   }
714 
715   S_node_unlink(sibling);
716 
717   cmark_node *old_next = node->next;
718 
719   // Insert 'sibling' between 'node' and 'old_next'.
720   if (old_next) {
721     old_next->prev = sibling;
722   }
723   sibling->next = old_next;
724   sibling->prev = node;
725   node->next = sibling;
726 
727   // Set new parent.
728   cmark_node *parent = node->parent;
729   sibling->parent = parent;
730 
731   // Adjust last_child of parent if inserted as last child.
732   if (parent && !old_next) {
733     parent->last_child = sibling;
734   }
735 
736   return 1;
737 }
738 
cmark_node_replace(cmark_node * oldnode,cmark_node * newnode)739 int cmark_node_replace(cmark_node *oldnode, cmark_node *newnode) {
740   if (!cmark_node_insert_before(oldnode, newnode)) {
741     return 0;
742   }
743   cmark_node_unlink(oldnode);
744   return 1;
745 }
746 
cmark_node_prepend_child(cmark_node * node,cmark_node * child)747 int cmark_node_prepend_child(cmark_node *node, cmark_node *child) {
748   if (!S_can_contain(node, child)) {
749     return 0;
750   }
751 
752   S_node_unlink(child);
753 
754   cmark_node *old_first_child = node->first_child;
755 
756   child->next = old_first_child;
757   child->prev = NULL;
758   child->parent = node;
759   node->first_child = child;
760 
761   if (old_first_child) {
762     old_first_child->prev = child;
763   } else {
764     // Also set last_child if node previously had no children.
765     node->last_child = child;
766   }
767 
768   return 1;
769 }
770 
cmark_node_append_child(cmark_node * node,cmark_node * child)771 int cmark_node_append_child(cmark_node *node, cmark_node *child) {
772   if (!S_can_contain(node, child)) {
773     return 0;
774   }
775 
776   S_node_unlink(child);
777 
778   cmark_node *old_last_child = node->last_child;
779 
780   child->next = NULL;
781   child->prev = old_last_child;
782   child->parent = node;
783   node->last_child = child;
784 
785   if (old_last_child) {
786     old_last_child->next = child;
787   } else {
788     // Also set first_child if node previously had no children.
789     node->first_child = child;
790   }
791 
792   return 1;
793 }
794 
S_print_error(FILE * out,cmark_node * node,const char * elem)795 static void S_print_error(FILE *out, cmark_node *node, const char *elem) {
796   if (out == NULL) {
797     return;
798   }
799   fprintf(out, "Invalid '%s' in node type %s at %d:%d\n", elem,
800           cmark_node_get_type_string(node), node->start_line,
801           node->start_column);
802 }
803 
cmark_node_check(cmark_node * node,FILE * out)804 int cmark_node_check(cmark_node *node, FILE *out) {
805   cmark_node *cur;
806   int errors = 0;
807 
808   if (!node) {
809     return 0;
810   }
811 
812   cur = node;
813   for (;;) {
814     if (cur->first_child) {
815       if (cur->first_child->prev != NULL) {
816         S_print_error(out, cur->first_child, "prev");
817         cur->first_child->prev = NULL;
818         ++errors;
819       }
820       if (cur->first_child->parent != cur) {
821         S_print_error(out, cur->first_child, "parent");
822         cur->first_child->parent = cur;
823         ++errors;
824       }
825       cur = cur->first_child;
826       continue;
827     }
828 
829   next_sibling:
830     if (cur == node) {
831       break;
832     }
833     if (cur->next) {
834       if (cur->next->prev != cur) {
835         S_print_error(out, cur->next, "prev");
836         cur->next->prev = cur;
837         ++errors;
838       }
839       if (cur->next->parent != cur->parent) {
840         S_print_error(out, cur->next, "parent");
841         cur->next->parent = cur->parent;
842         ++errors;
843       }
844       cur = cur->next;
845       continue;
846     }
847 
848     if (cur->parent->last_child != cur) {
849       S_print_error(out, cur->parent, "last_child");
850       cur->parent->last_child = cur;
851       ++errors;
852     }
853     cur = cur->parent;
854     goto next_sibling;
855   }
856 
857   return errors;
858 }
859