From: Olaf Wintermann Date: Sat, 26 Apr 2025 10:40:28 +0000 (+0200) Subject: parse MD lists into AST X-Git-Url: https://uap-core.de/gitweb/?a=commitdiff_plain;h=b7a54e067fc728a1589afe5f673c7c8615f322d7;p=note.git parse MD lists into AST --- diff --git a/application/editor.c b/application/editor.c index 7bfdec4..89370cf 100644 --- a/application/editor.c +++ b/application/editor.c @@ -60,6 +60,7 @@ typedef struct MDParserData { MDDoc *doc; MDPara *p_current; CxList *text_stack; + int list_depth; const CxAllocator *a; } MDParserData; @@ -74,6 +75,44 @@ static MDNode* get_current_textnode(MDParserData *p) { return NULL; } +static void md_node_add_child(MDNode *node, MDNode *child) { + cx_linked_list_add((void**)&node->children_begin, (void**)&node->children_end, -1, offsetof(MDNode, next), child); + child->parent = node; +} + +static void textstack_update_last_element(CxList *stack_list, MDNode *t) { + size_t len = cxListSize(stack_list); + if(len > 0) { + // replace this, when something like cxListSet is available + cxListRemove(stack_list, len-1); + cxListAdd(stack_list, t); + } +} + +// create a node and add it to the current paragraph node tree +static MDNode* md_node_create(MDParserData *data) { + MDNode *current = get_current_textnode(data); + size_t len = cxListSize(data->text_stack); + + MDNode *node = cxCalloc(data->a, 1, sizeof(MDNode)); + if(current) { + if(current->closed) { + current->next = node; + if(current->parent) { + current->parent->children_end = node; + } + textstack_update_last_element(data->text_stack, node); + } else { + md_node_add_child(current, node); + } + + } else { + data->p_current->content = node; + } + cxListAdd(data->text_stack, node); + return node; +} + static int md_enter_block(MD_BLOCKTYPE type, void* detail, void* userdata) { MDParserData *data = userdata; @@ -81,7 +120,21 @@ static int md_enter_block(MD_BLOCKTYPE type, void* detail, void* userdata) { return 0; } - // TODO: li + if(type == MD_BLOCK_UL || type == MD_BLOCK_OL) { + // UL can be inserted as paragraph (toplevel list) or as + // MDNode (nested list) + if(++data->list_depth > 1) { + // TODO + //MDNode *node = cxCalloc(data->a, 1, sizeof(MDNode)); + //node->type0 = MD_BLOCK_UL; + return 0; + } + } else if(type == MD_BLOCK_LI) { + // LI list elements are always added as MDNode* + MDNode *node = md_node_create(data); + node->type0 = type; + return 0; + } // create empty paragraph node and add it to the document MDPara *p = cxMalloc(data->a, sizeof(MDPara)); @@ -107,46 +160,16 @@ static int md_enter_block(MD_BLOCKTYPE type, void* detail, void* userdata) { return 0; } -static int md_leave_block(MD_BLOCKTYPE type, void* detail, void* userdata) { - return 0; -} +static int md_leave_span(MD_SPANTYPE type, void* detail, void* userdata); -static void textstack_update_last_element(CxList *stack_list, MDNode *t) { - size_t len = cxListSize(stack_list); - if(len > 0) { - // replace this, when something like cxListSet is available - cxListRemove(stack_list, len-1); - cxListAdd(stack_list, t); - } -} - -static void md_node_add_child(MDNode *node, MDNode *child) { - cx_linked_list_add((void**)&node->children_begin, (void**)&node->children_end, -1, offsetof(MDNode, next), child); - child->parent = node; -} - -// create a node and add it to the current paragraph node tree -static MDNode* md_node_create(MDParserData *data) { - MDNode *current = get_current_textnode(data); - size_t len = cxListSize(data->text_stack); - - MDNode *node = cxCalloc(data->a, 1, sizeof(MDNode)); - if(current) { - if(current->closed) { - current->next = node; - if(current->parent) { - current->parent->children_end = node; - } - textstack_update_last_element(data->text_stack, node); - } else { - md_node_add_child(current, node); - } - - } else { - data->p_current->content = node; +static int md_leave_block(MD_BLOCKTYPE type, void* detail, void* userdata) { + MDParserData *data = userdata; + if(type == MD_BLOCK_UL || type == MD_BLOCK_OL) { + data->list_depth--; + } else if(type == MD_BLOCK_LI) { + md_leave_span(0, NULL, data); } - cxListAdd(data->text_stack, node); - return node; + return 0; } static int md_enter_span(MD_SPANTYPE type, void* detail, void* userdata) { @@ -264,6 +287,7 @@ MDDoc* parse_markdown(cxstring markdown) { data.doc = doc; data.p_current = NULL; data.text_stack = cxArrayListCreateSimple(CX_STORE_POINTERS, 16); + data.list_depth = 0; data.a = a; MD_PARSER parser = {0}; @@ -355,6 +379,7 @@ static const char* paragraph_style(MDPara *p) { } case MD_BLOCK_QUOTE: return EDITOR_STYLE_QUOTE; case MD_BLOCK_CODE: return EDITOR_STYLE_CODE_BLOCK; + case MD_BLOCK_UL: return EDITOR_STYLE_LIST0; default: return EDITOR_STYLE_PARAGRAPH; } } diff --git a/application/editor.h b/application/editor.h index ae4bb99..954c947 100644 --- a/application/editor.h +++ b/application/editor.h @@ -77,6 +77,7 @@ struct MDNode { MDNode *parent; cxmutstr text; cxmutstr link; + MD_BLOCKTYPE type0; MD_SPANTYPE type; MDNode *children_begin; MDNode *children_end; diff --git a/application/tests/test-editor.c b/application/tests/test-editor.c index 6d275ec..a0b274b 100644 --- a/application/tests/test-editor.c +++ b/application/tests/test-editor.c @@ -241,6 +241,60 @@ CX_TEST(test_parse_markdown_formatting_nested) { } } +CX_TEST(test_parse_markdown_list) { + MDDoc *doc; + MDPara *p0; // Test Paragraph + MDPara *p1; // List + MDPara *p2; // end + MDNode *li0; + MDNode *li1; + MDNode *li0text; + MDNode *li1text; + MDNode *li1text_bold; + + CX_TEST_DO { + doc = parse_markdown(CX_STR("Test Paragraph\n\n - List1\n - List2 __bold__\n\nend")); + CX_TEST_ASSERT(doc); + + // paragraph structure + CX_TEST_ASSERT(doc->content); + p0 = doc->content; + CX_TEST_ASSERT(p0->type == MD_BLOCK_P); + + CX_TEST_ASSERT(p0->next); + p1 = p0->next; + CX_TEST_ASSERT(p1->type = MD_BLOCK_UL); + + CX_TEST_ASSERT(p1->next); + p2 = p1->next; + CX_TEST_ASSERT(p2->type == MD_BLOCK_P); + + // check list + li0 = p1->content; + CX_TEST_ASSERT(li0); + li1 = li0->next; + CX_TEST_ASSERT(li1); + + li0text = li0->children_begin; + CX_TEST_ASSERT(li0text); + CX_TEST_ASSERT(li0text->text.ptr != NULL); + CX_TEST_ASSERT(!cx_strcmp(mdnode_get_text(li0text), CX_STR("List1"))); + + li1text = li1->children_begin; + CX_TEST_ASSERT(li1text); + CX_TEST_ASSERT(li1text->text.ptr != NULL); + CX_TEST_ASSERT(!cx_strcmp(mdnode_get_text(li1text), CX_STR("List2 "))); + li1text_bold = li1text->next; + CX_TEST_ASSERT(li1text_bold); + CX_TEST_ASSERT(li1text_bold->type == MD_SPAN_STRONG); + CX_TEST_ASSERT(!cx_strcmp(mdnode_get_text(li1text_bold), CX_STR("bold"))); + + // check end paragraph + CX_TEST_ASSERT(p2->content != NULL); + CX_TEST_ASSERT(!cx_strcmp(mdnode_get_text(p2->content), CX_STR("end"))); + } +} + static int str_eq(const char *s1, const char *s2) { if(!s1) { return s1 == s2; diff --git a/application/tests/test-editor.h b/application/tests/test-editor.h index 6894146..c049451 100644 --- a/application/tests/test-editor.h +++ b/application/tests/test-editor.h @@ -38,6 +38,7 @@ extern "C" { CX_TEST(test_parse_markdown_para); CX_TEST(test_parse_markdown_formatting_simple); CX_TEST(test_parse_markdown_formatting_nested); +CX_TEST(test_parse_markdown_list); CX_TEST(test_mddoc_linearization); #ifdef __cplusplus diff --git a/application/tests/testmain.c b/application/tests/testmain.c index 67a05c2..f65c907 100644 --- a/application/tests/testmain.c +++ b/application/tests/testmain.c @@ -36,6 +36,7 @@ int main(int argc, char **argv) { cx_test_register(suite, test_parse_markdown_para); cx_test_register(suite, test_parse_markdown_formatting_simple); cx_test_register(suite, test_parse_markdown_formatting_nested); + cx_test_register(suite, test_parse_markdown_list); cx_test_register(suite, test_mddoc_linearization); cx_test_run_stdout(suite);