From add3952dfbad4b60db2f00c3be80ab1a577c1568 Mon Sep 17 00:00:00 2001 From: QuietMisdreavus Date: Sat, 6 May 2023 16:32:39 -0600 Subject: [PATCH 1/5] report footnotes in cmark_node_get_type_string --- src/node.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/node.c b/src/node.c index d93ed307d..5a990aaf2 100644 --- a/src/node.c +++ b/src/node.c @@ -313,6 +313,10 @@ const char *cmark_node_get_type_string(cmark_node *node) { return "link"; case CMARK_NODE_IMAGE: return "image"; + case CMARK_NODE_FOOTNOTE_REFERENCE: + return "footnote_reference"; + case CMARK_NODE_FOOTNOTE_DEFINITION: + return "footnote_definition"; case CMARK_NODE_ATTRIBUTE: return "attribute"; } From cb6ce978005a18cab76d8b22872409d2ac718ca0 Mon Sep 17 00:00:00 2001 From: QuietMisdreavus Date: Sat, 6 May 2023 16:34:20 -0600 Subject: [PATCH 2/5] emit footnote IDs in XML output --- src/xml.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/xml.c b/src/xml.c index 184bff486..50fbb3c72 100644 --- a/src/xml.c +++ b/src/xml.c @@ -137,6 +137,16 @@ static int S_render_node(cmark_node *node, cmark_event_type ev_type, case CMARK_NODE_ATTRIBUTE: // TODO break; + case CMARK_NODE_FOOTNOTE_DEFINITION: + cmark_strbuf_puts(xml, " id=\""); + escape_xml(xml, node->as.literal.data, node->as.literal.len); + cmark_strbuf_putc(xml, '"'); + break; + case CMARK_NODE_FOOTNOTE_REFERENCE: + cmark_strbuf_puts(xml, " id=\""); + escape_xml(xml, node->parent_footnote_def->as.literal.data, + node->parent_footnote_def->as.literal.len); + cmark_strbuf_putc(xml, '"'); default: break; } From c62200dc1d02bbbaea81c18f18eb4fe6da37e459 Mon Sep 17 00:00:00 2001 From: QuietMisdreavus Date: Sat, 6 May 2023 16:58:33 -0600 Subject: [PATCH 3/5] allocate footnote definition cstr after length adjustment --- src/blocks.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/blocks.c b/src/blocks.c index a7290652c..0ed2a71e8 100644 --- a/src/blocks.c +++ b/src/blocks.c @@ -1249,12 +1249,15 @@ static void open_new_blocks(cmark_parser *parser, cmark_node **container, parser->options & CMARK_OPT_FOOTNOTES && (matched = scan_footnote_definition(input, parser->first_nonspace))) { cmark_chunk c = cmark_chunk_dup(input, parser->first_nonspace + 2, matched - 2); - cmark_chunk_to_cstr(parser->mem, &c); while (c.data[c.len - 1] != ']') --c.len; --c.len; + // Allocate the cstr rendering after the length adjustment so that the + // length is accurate when `cmark_node_get_literal` is called. + cmark_chunk_to_cstr(parser->mem, &c); + S_advance_offset(parser, input, parser->first_nonspace + matched - parser->offset, false); *container = add_child(parser, *container, CMARK_NODE_FOOTNOTE_DEFINITION, parser->first_nonspace + matched + 1); (*container)->as.literal = c; From 0174f5d424a23c6d20726c6d0c19157b6bffb1e9 Mon Sep 17 00:00:00 2001 From: QuietMisdreavus Date: Sat, 6 May 2023 16:59:01 -0600 Subject: [PATCH 4/5] add cmark_node_get_footnote_id function --- src/include/cmark-gfm.h | 5 +++++ src/node.c | 18 ++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/src/include/cmark-gfm.h b/src/include/cmark-gfm.h index 43b8893c8..00b43372a 100644 --- a/src/include/cmark-gfm.h +++ b/src/include/cmark-gfm.h @@ -368,6 +368,11 @@ CMARK_GFM_EXPORT const char *cmark_node_get_literal(cmark_node *node); */ CMARK_GFM_EXPORT int cmark_node_get_backtick_count(cmark_node *node); +/** If 'node' is a footnote reference or footnote definition, reutrns the + string ID of that footnote, otherwise returns NULL. + */ +CMARK_GFM_EXPORT const char *cmark_node_get_footnote_id(cmark_node *node); + /** Sets the string contents of 'node'. Returns 1 on success, * 0 on failure. */ diff --git a/src/node.c b/src/node.c index 5a990aaf2..dfdd59b28 100644 --- a/src/node.c +++ b/src/node.c @@ -462,6 +462,24 @@ int cmark_node_set_literal(cmark_node *node, const char *content) { return 0; } +const char *cmark_node_get_footnote_id(cmark_node *node) { + if (node == NULL) + return NULL; + + switch (node->type) { + case CMARK_NODE_FOOTNOTE_DEFINITION: + return cmark_chunk_to_cstr(NODE_MEM(node), &node->as.literal); + + case CMARK_NODE_FOOTNOTE_REFERENCE: + return cmark_chunk_to_cstr(NODE_MEM(node->parent_footnote_def), &node->parent_footnote_def->as.literal); + + default: + break; + } + + return NULL; +} + const char *cmark_node_get_string_content(cmark_node *node) { return (char *) node->content.ptr; } From fdb19de06187abb6840e966d66cbdde4dd54480e Mon Sep 17 00:00:00 2001 From: QuietMisdreavus Date: Sat, 6 May 2023 17:19:58 -0600 Subject: [PATCH 5/5] add test for footnote XML rendering and metadata --- api_test/main.c | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/api_test/main.c b/api_test/main.c index e950451ba..00e67c769 100644 --- a/api_test/main.c +++ b/api_test/main.c @@ -1571,6 +1571,49 @@ static void table_spans(test_batch_runner *runner) { } } +static void footnote_metadata(test_batch_runner *runner) { + static const char markdown[] = + "this is a test[^test] and also a test[^other]\n" + "\n" + "[^test]: still a test\n" + "\n" + "[^other]: lorem ipsum\n"; + static const char expected[] = + "\n" + "\n" + "\n" + " \n" + " this is a test\n" + " \n" + " and also a test\n" + " \n" + " \n" + " \n" + " \n" + " still a test\n" + " \n" + " \n" + " \n" + " \n" + " lorem ipsum\n" + " \n" + " \n" + "\n"; + + cmark_node *doc = + cmark_parse_document(markdown, sizeof(markdown) - 1, CMARK_OPT_FOOTNOTES); + + const char *xml = cmark_render_xml(doc, CMARK_OPT_DEFAULT); + + STR_EQ(runner, xml, expected, "footnotes render correctly in XML"); + + cmark_node *test_def = cmark_node_nth_child(doc, 1); + STR_EQ(runner, cmark_node_get_footnote_id(test_def), "test", "footnote ID parsed properly"); + + cmark_node *other_def = cmark_node_nth_child(doc, 2); + STR_EQ(runner, cmark_node_get_footnote_id(other_def), "other", "footnote ID parsed properly"); +} + int main() { int retval; test_batch_runner *runner = test_batch_runner_new(); @@ -1608,6 +1651,7 @@ int main() { verify_custom_attributes_node_with_footnote(runner); parser_interrupt(runner); table_spans(runner); + footnote_metadata(runner); test_print_summary(runner); retval = test_ok(runner) ? 0 : 1;