apex-ruby 1.0.6 → 1.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/ext/apex_ext/apex_ext.c +6 -0
- data/ext/apex_ext/apex_src/AGENTS.md +41 -0
- data/ext/apex_ext/apex_src/CHANGELOG.md +412 -2
- data/ext/apex_ext/apex_src/CMakeLists.txt +41 -29
- data/ext/apex_ext/apex_src/Formula/apex.rb +2 -2
- data/ext/apex_ext/apex_src/Package.swift +9 -0
- data/ext/apex_ext/apex_src/README.md +31 -9
- data/ext/apex_ext/apex_src/ROADMAP.md +5 -0
- data/ext/apex_ext/apex_src/VERSION +1 -1
- data/ext/apex_ext/apex_src/cli/main.c +1125 -13
- data/ext/apex_ext/apex_src/docs/index.md +459 -0
- data/ext/apex_ext/apex_src/include/apex/apex.h +67 -5
- data/ext/apex_ext/apex_src/include/apex/ast_man.h +20 -0
- data/ext/apex_ext/apex_src/include/apex/ast_markdown.h +39 -0
- data/ext/apex_ext/apex_src/include/apex/ast_terminal.h +40 -0
- data/ext/apex_ext/apex_src/include/apex/module.modulemap +1 -1
- data/ext/apex_ext/apex_src/man/apex-config.5 +333 -258
- data/ext/apex_ext/apex_src/man/apex-config.5.md +3 -1
- data/ext/apex_ext/apex_src/man/apex-plugins.7 +401 -316
- data/ext/apex_ext/apex_src/man/apex.1 +663 -620
- data/ext/apex_ext/apex_src/man/apex.1.html +703 -0
- data/ext/apex_ext/apex_src/man/apex.1.md +160 -90
- data/ext/apex_ext/apex_src/objc/Apex.swift +6 -0
- data/ext/apex_ext/apex_src/objc/NSString+Apex.h +12 -0
- data/ext/apex_ext/apex_src/objc/NSString+Apex.m +9 -0
- data/ext/apex_ext/apex_src/pages/index.md +459 -0
- data/ext/apex_ext/apex_src/src/_README.md +4 -4
- data/ext/apex_ext/apex_src/src/apex.c +702 -44
- data/ext/apex_ext/apex_src/src/ast_json.c +1130 -0
- data/ext/apex_ext/apex_src/src/ast_json.h +46 -0
- data/ext/apex_ext/apex_src/src/ast_man.c +948 -0
- data/ext/apex_ext/apex_src/src/ast_markdown.c +409 -0
- data/ext/apex_ext/apex_src/src/ast_terminal.c +2516 -0
- data/ext/apex_ext/apex_src/src/extensions/abbreviations.c +8 -5
- data/ext/apex_ext/apex_src/src/extensions/definition_list.c +491 -1514
- data/ext/apex_ext/apex_src/src/extensions/definition_list.h +8 -15
- data/ext/apex_ext/apex_src/src/extensions/emoji.c +207 -0
- data/ext/apex_ext/apex_src/src/extensions/emoji.h +14 -0
- data/ext/apex_ext/apex_src/src/extensions/header_ids.c +178 -71
- data/ext/apex_ext/apex_src/src/extensions/highlight.c +37 -5
- data/ext/apex_ext/apex_src/src/extensions/ial.c +416 -47
- data/ext/apex_ext/apex_src/src/extensions/includes.c +241 -10
- data/ext/apex_ext/apex_src/src/extensions/includes.h +1 -0
- data/ext/apex_ext/apex_src/src/extensions/metadata.c +166 -3
- data/ext/apex_ext/apex_src/src/extensions/metadata.h +7 -0
- data/ext/apex_ext/apex_src/src/extensions/sup_sub.c +34 -3
- data/ext/apex_ext/apex_src/src/extensions/syntax_highlight.c +55 -10
- data/ext/apex_ext/apex_src/src/extensions/syntax_highlight.h +7 -4
- data/ext/apex_ext/apex_src/src/extensions/table_html_postprocess.c +84 -52
- data/ext/apex_ext/apex_src/src/extensions/toc.c +133 -19
- data/ext/apex_ext/apex_src/src/filters_ast.c +194 -0
- data/ext/apex_ext/apex_src/src/filters_ast.h +36 -0
- data/ext/apex_ext/apex_src/src/html_renderer.c +1265 -35
- data/ext/apex_ext/apex_src/src/html_renderer.h +21 -0
- data/ext/apex_ext/apex_src/src/plugins_remote.c +40 -14
- data/ext/apex_ext/apex_src/tests/CMakeLists.txt +1 -0
- data/ext/apex_ext/apex_src/tests/README.md +11 -5
- data/ext/apex_ext/apex_src/tests/fixtures/comprehensive_test.md +13 -2
- data/ext/apex_ext/apex_src/tests/fixtures/filters/filter_output_with_rawblock.json +1 -0
- data/ext/apex_ext/apex_src/tests/fixtures/filters/unwrap.md +7 -0
- data/ext/apex_ext/apex_src/tests/fixtures/images/auto-wildcard.md +8 -0
- data/ext/apex_ext/apex_src/tests/fixtures/images/img/app-pass-1-profile-menu.avif +0 -0
- data/ext/apex_ext/apex_src/tests/fixtures/images/img/app-pass-1-profile-menu.jpg +0 -0
- data/ext/apex_ext/apex_src/tests/fixtures/images/img/app-pass-1-profile-menu.webp +0 -0
- data/ext/apex_ext/apex_src/tests/fixtures/images/img/app-pass-1-profile-menu@2x.avif +0 -0
- data/ext/apex_ext/apex_src/tests/fixtures/images/img/app-pass-1-profile-menu@2x.jpg +0 -0
- data/ext/apex_ext/apex_src/tests/fixtures/images/img/app-pass-1-profile-menu@2x.webp +0 -0
- data/ext/apex_ext/apex_src/tests/fixtures/images/media_formats_test.md +63 -0
- data/ext/apex_ext/apex_src/tests/fixtures/includes/data-semi.csv +3 -0
- data/ext/apex_ext/apex_src/tests/fixtures/includes/with space.txt +1 -0
- data/ext/apex_ext/apex_src/tests/fixtures/tables/inline_tables_test.md +4 -1
- data/ext/apex_ext/apex_src/tests/paginate_cli_test.sh +64 -0
- data/ext/apex_ext/apex_src/tests/terminal_width_test.sh +29 -0
- data/ext/apex_ext/apex_src/tests/test-swift-package.sh +14 -0
- data/ext/apex_ext/apex_src/tests/test_cmark_callback.c +189 -0
- data/ext/apex_ext/apex_src/tests/test_extensions.c +374 -0
- data/ext/apex_ext/apex_src/tests/test_metadata.c +68 -0
- data/ext/apex_ext/apex_src/tests/test_output.c +291 -2
- data/ext/apex_ext/apex_src/tests/test_runner.c +10 -0
- data/ext/apex_ext/apex_src/tests/test_syntax_highlight.c +1 -1
- data/ext/apex_ext/apex_src/tests/test_tables.c +17 -1
- data/lib/apex/version.rb +1 -1
- metadata +32 -2
- data/ext/apex_ext/apex_src/docs/FUTURE_FEATURES.md +0 -456
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ast_markdown.c - Convert cmark-gfm AST to Markdown
|
|
3
|
+
*
|
|
4
|
+
* Supports multiple Markdown dialects with different formatting rules.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
#include "apex/ast_markdown.h"
|
|
8
|
+
#include "apex/apex.h"
|
|
9
|
+
#include "cmark-gfm.h"
|
|
10
|
+
#include <stdlib.h>
|
|
11
|
+
#include <string.h>
|
|
12
|
+
#include <stdio.h>
|
|
13
|
+
|
|
14
|
+
/* Buffer for building markdown output */
|
|
15
|
+
typedef struct {
|
|
16
|
+
char *buf;
|
|
17
|
+
size_t len;
|
|
18
|
+
size_t capacity;
|
|
19
|
+
} markdown_buffer;
|
|
20
|
+
|
|
21
|
+
static void buffer_init(markdown_buffer *buf) {
|
|
22
|
+
buf->buf = NULL;
|
|
23
|
+
buf->len = 0;
|
|
24
|
+
buf->capacity = 0;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
static void buffer_append(markdown_buffer *buf, const char *str, size_t len) {
|
|
28
|
+
if (!str || len == 0) return;
|
|
29
|
+
|
|
30
|
+
if (buf->len + len + 1 > buf->capacity) {
|
|
31
|
+
size_t new_cap = buf->capacity ? buf->capacity * 2 : 256;
|
|
32
|
+
if (new_cap < buf->len + len + 1) {
|
|
33
|
+
new_cap = buf->len + len + 1;
|
|
34
|
+
}
|
|
35
|
+
char *new_buf = realloc(buf->buf, new_cap);
|
|
36
|
+
if (!new_buf) return;
|
|
37
|
+
buf->buf = new_buf;
|
|
38
|
+
buf->capacity = new_cap;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
memcpy(buf->buf + buf->len, str, len);
|
|
42
|
+
buf->len += len;
|
|
43
|
+
buf->buf[buf->len] = '\0';
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
static void buffer_append_str(markdown_buffer *buf, const char *str) {
|
|
47
|
+
if (str) {
|
|
48
|
+
buffer_append(buf, str, strlen(str));
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
/* Escape special characters in markdown */
|
|
54
|
+
static void escape_markdown(markdown_buffer *buf, const char *text, size_t len, apex_markdown_dialect_t dialect) {
|
|
55
|
+
if (!text) return;
|
|
56
|
+
|
|
57
|
+
for (size_t i = 0; i < len; i++) {
|
|
58
|
+
char c = text[i];
|
|
59
|
+
bool should_escape = false;
|
|
60
|
+
|
|
61
|
+
switch (c) {
|
|
62
|
+
case '\\':
|
|
63
|
+
case '`':
|
|
64
|
+
case '*':
|
|
65
|
+
case '_':
|
|
66
|
+
case '[':
|
|
67
|
+
case ']':
|
|
68
|
+
case '(':
|
|
69
|
+
case ')':
|
|
70
|
+
case '#':
|
|
71
|
+
case '+':
|
|
72
|
+
case '.':
|
|
73
|
+
case '!':
|
|
74
|
+
should_escape = true;
|
|
75
|
+
break;
|
|
76
|
+
case '{':
|
|
77
|
+
case '}':
|
|
78
|
+
/* Don't escape braces for MMD (used in {{TOC}} syntax) */
|
|
79
|
+
if (dialect != APEX_MD_DIALECT_MMD) {
|
|
80
|
+
should_escape = true;
|
|
81
|
+
}
|
|
82
|
+
break;
|
|
83
|
+
case '-':
|
|
84
|
+
/* Don't escape dashes for MMD (used in TOC parameters like {{TOC:2-4}}) */
|
|
85
|
+
if (dialect != APEX_MD_DIALECT_MMD) {
|
|
86
|
+
should_escape = true;
|
|
87
|
+
}
|
|
88
|
+
break;
|
|
89
|
+
default:
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (should_escape) {
|
|
94
|
+
buffer_append(buf, "\\", 1);
|
|
95
|
+
}
|
|
96
|
+
buffer_append(buf, &c, 1);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/* Serialize inline nodes */
|
|
101
|
+
static void serialize_inline(markdown_buffer *buf, cmark_node *node, apex_markdown_dialect_t dialect) {
|
|
102
|
+
if (!node) return;
|
|
103
|
+
|
|
104
|
+
cmark_node_type type = cmark_node_get_type(node);
|
|
105
|
+
const char *literal = cmark_node_get_literal(node);
|
|
106
|
+
|
|
107
|
+
switch (type) {
|
|
108
|
+
case CMARK_NODE_TEXT:
|
|
109
|
+
if (literal) {
|
|
110
|
+
/* For MMD, check if this text contains a TOC marker and convert it */
|
|
111
|
+
if (dialect == APEX_MD_DIALECT_MMD) {
|
|
112
|
+
const char *toc_start = strstr(literal, "{{TOC");
|
|
113
|
+
if (toc_start) {
|
|
114
|
+
/* Found TOC marker - output text before it */
|
|
115
|
+
if (toc_start > literal) {
|
|
116
|
+
escape_markdown(buf, literal, (size_t)(toc_start - literal), dialect);
|
|
117
|
+
}
|
|
118
|
+
/* Find the end of the TOC marker */
|
|
119
|
+
const char *toc_end = strstr(toc_start, "}}");
|
|
120
|
+
if (toc_end) {
|
|
121
|
+
/* Found complete TOC marker - output just {{TOC}} for MMD (no parameters) */
|
|
122
|
+
buffer_append_str(buf, "{{TOC}}");
|
|
123
|
+
/* Output any remaining text after the marker */
|
|
124
|
+
const char *remaining = toc_end + 2;
|
|
125
|
+
if (*remaining) {
|
|
126
|
+
escape_markdown(buf, remaining, strlen(remaining), dialect);
|
|
127
|
+
}
|
|
128
|
+
break;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
/* Normal text - escape as needed */
|
|
133
|
+
escape_markdown(buf, literal, strlen(literal), dialect);
|
|
134
|
+
}
|
|
135
|
+
break;
|
|
136
|
+
|
|
137
|
+
case CMARK_NODE_SOFTBREAK:
|
|
138
|
+
buffer_append_str(buf, "\n");
|
|
139
|
+
break;
|
|
140
|
+
|
|
141
|
+
case CMARK_NODE_LINEBREAK:
|
|
142
|
+
buffer_append_str(buf, " \n");
|
|
143
|
+
break;
|
|
144
|
+
|
|
145
|
+
case CMARK_NODE_CODE:
|
|
146
|
+
if (literal) {
|
|
147
|
+
buffer_append_str(buf, "`");
|
|
148
|
+
buffer_append_str(buf, literal);
|
|
149
|
+
buffer_append_str(buf, "`");
|
|
150
|
+
}
|
|
151
|
+
break;
|
|
152
|
+
|
|
153
|
+
case CMARK_NODE_EMPH:
|
|
154
|
+
buffer_append_str(buf, "*");
|
|
155
|
+
for (cmark_node *child = cmark_node_first_child(node); child; child = cmark_node_next(child)) {
|
|
156
|
+
serialize_inline(buf, child, dialect);
|
|
157
|
+
}
|
|
158
|
+
buffer_append_str(buf, "*");
|
|
159
|
+
break;
|
|
160
|
+
|
|
161
|
+
case CMARK_NODE_STRONG:
|
|
162
|
+
buffer_append_str(buf, "**");
|
|
163
|
+
for (cmark_node *child = cmark_node_first_child(node); child; child = cmark_node_next(child)) {
|
|
164
|
+
serialize_inline(buf, child, dialect);
|
|
165
|
+
}
|
|
166
|
+
buffer_append_str(buf, "**");
|
|
167
|
+
break;
|
|
168
|
+
|
|
169
|
+
case CMARK_NODE_LINK:
|
|
170
|
+
buffer_append_str(buf, "[");
|
|
171
|
+
for (cmark_node *child = cmark_node_first_child(node); child; child = cmark_node_next(child)) {
|
|
172
|
+
if (cmark_node_get_type(child) != CMARK_NODE_LINK) {
|
|
173
|
+
serialize_inline(buf, child, dialect);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
buffer_append_str(buf, "](");
|
|
177
|
+
const char *url = cmark_node_get_url(node);
|
|
178
|
+
if (url) buffer_append_str(buf, url);
|
|
179
|
+
const char *title = cmark_node_get_title(node);
|
|
180
|
+
if (title) {
|
|
181
|
+
buffer_append_str(buf, " \"");
|
|
182
|
+
buffer_append_str(buf, title);
|
|
183
|
+
buffer_append_str(buf, "\"");
|
|
184
|
+
}
|
|
185
|
+
buffer_append_str(buf, ")");
|
|
186
|
+
break;
|
|
187
|
+
|
|
188
|
+
case CMARK_NODE_IMAGE:
|
|
189
|
+
buffer_append_str(buf, ";
|
|
193
|
+
url = cmark_node_get_url(node);
|
|
194
|
+
if (url) buffer_append_str(buf, url);
|
|
195
|
+
title = cmark_node_get_title(node);
|
|
196
|
+
if (title) {
|
|
197
|
+
buffer_append_str(buf, " \"");
|
|
198
|
+
buffer_append_str(buf, title);
|
|
199
|
+
buffer_append_str(buf, "\"");
|
|
200
|
+
}
|
|
201
|
+
buffer_append_str(buf, ")");
|
|
202
|
+
break;
|
|
203
|
+
|
|
204
|
+
default:
|
|
205
|
+
/* For other inline types, serialize children */
|
|
206
|
+
for (cmark_node *child = cmark_node_first_child(node); child; child = cmark_node_next(child)) {
|
|
207
|
+
serialize_inline(buf, child, dialect);
|
|
208
|
+
}
|
|
209
|
+
break;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/* Serialize block nodes */
|
|
214
|
+
static void serialize_block(markdown_buffer *buf, cmark_node *node, apex_markdown_dialect_t dialect, int indent_level) {
|
|
215
|
+
if (!node) return;
|
|
216
|
+
|
|
217
|
+
cmark_node_type type = cmark_node_get_type(node);
|
|
218
|
+
|
|
219
|
+
switch (type) {
|
|
220
|
+
case CMARK_NODE_DOCUMENT:
|
|
221
|
+
for (cmark_node *child = cmark_node_first_child(node); child; child = cmark_node_next(child)) {
|
|
222
|
+
serialize_block(buf, child, dialect, indent_level);
|
|
223
|
+
if (cmark_node_get_type(child) != CMARK_NODE_LIST &&
|
|
224
|
+
cmark_node_get_type(child) != CMARK_NODE_ITEM) {
|
|
225
|
+
buffer_append_str(buf, "\n");
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
break;
|
|
229
|
+
|
|
230
|
+
case CMARK_NODE_PARAGRAPH: {
|
|
231
|
+
/* Check if paragraph contains only a TOC marker (for MMD dialect) */
|
|
232
|
+
bool is_toc_paragraph = false;
|
|
233
|
+
if (dialect == APEX_MD_DIALECT_MMD) {
|
|
234
|
+
/* Check if this paragraph contains only text that looks like {{TOC...}} */
|
|
235
|
+
cmark_node *first_child = cmark_node_first_child(node);
|
|
236
|
+
if (first_child && cmark_node_get_type(first_child) == CMARK_NODE_TEXT &&
|
|
237
|
+
!cmark_node_next(first_child)) {
|
|
238
|
+
const char *text = cmark_node_get_literal(first_child);
|
|
239
|
+
if (text && strstr(text, "{{TOC") == text) {
|
|
240
|
+
/* This is a TOC marker paragraph - convert to {{TOC}} for MMD */
|
|
241
|
+
is_toc_paragraph = true;
|
|
242
|
+
buffer_append_str(buf, "{{TOC}}\n\n");
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
if (!is_toc_paragraph) {
|
|
247
|
+
for (cmark_node *child = cmark_node_first_child(node); child; child = cmark_node_next(child)) {
|
|
248
|
+
serialize_inline(buf, child, dialect);
|
|
249
|
+
}
|
|
250
|
+
buffer_append_str(buf, "\n\n");
|
|
251
|
+
}
|
|
252
|
+
break;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
case CMARK_NODE_HEADING: {
|
|
256
|
+
int level = cmark_node_get_heading_level(node);
|
|
257
|
+
for (int i = 0; i < level; i++) {
|
|
258
|
+
buffer_append_str(buf, "#");
|
|
259
|
+
}
|
|
260
|
+
buffer_append_str(buf, " ");
|
|
261
|
+
for (cmark_node *child = cmark_node_first_child(node); child; child = cmark_node_next(child)) {
|
|
262
|
+
serialize_inline(buf, child, dialect);
|
|
263
|
+
}
|
|
264
|
+
buffer_append_str(buf, "\n\n");
|
|
265
|
+
break;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
case CMARK_NODE_CODE_BLOCK: {
|
|
269
|
+
const char *info = cmark_node_get_fence_info(node);
|
|
270
|
+
buffer_append_str(buf, "```");
|
|
271
|
+
if (info) buffer_append_str(buf, info);
|
|
272
|
+
buffer_append_str(buf, "\n");
|
|
273
|
+
const char *literal = cmark_node_get_literal(node);
|
|
274
|
+
if (literal) buffer_append_str(buf, literal);
|
|
275
|
+
buffer_append_str(buf, "\n```\n\n");
|
|
276
|
+
break;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
case CMARK_NODE_BLOCK_QUOTE: {
|
|
280
|
+
/* Serialize blockquote with > on same line as content */
|
|
281
|
+
for (cmark_node *child = cmark_node_first_child(node); child; child = cmark_node_next(child)) {
|
|
282
|
+
cmark_node_type child_type = cmark_node_get_type(child);
|
|
283
|
+
|
|
284
|
+
if (child_type == CMARK_NODE_PARAGRAPH) {
|
|
285
|
+
/* For paragraphs, serialize inline content directly on same line as > */
|
|
286
|
+
cmark_node *inline_child = cmark_node_first_child(child);
|
|
287
|
+
bool first_item = true;
|
|
288
|
+
|
|
289
|
+
while (inline_child) {
|
|
290
|
+
cmark_node_type inline_type = cmark_node_get_type(inline_child);
|
|
291
|
+
|
|
292
|
+
if (inline_type == CMARK_NODE_SOFTBREAK) {
|
|
293
|
+
/* For soft breaks in blockquotes, continue on next line with > */
|
|
294
|
+
buffer_append_str(buf, "\n> ");
|
|
295
|
+
first_item = false;
|
|
296
|
+
} else {
|
|
297
|
+
/* For first non-softbreak item, add > prefix */
|
|
298
|
+
if (first_item) {
|
|
299
|
+
buffer_append_str(buf, "> ");
|
|
300
|
+
first_item = false;
|
|
301
|
+
}
|
|
302
|
+
serialize_inline(buf, inline_child, dialect);
|
|
303
|
+
}
|
|
304
|
+
inline_child = cmark_node_next(inline_child);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
if (cmark_node_next(child)) {
|
|
308
|
+
buffer_append_str(buf, "\n");
|
|
309
|
+
} else {
|
|
310
|
+
buffer_append_str(buf, "\n\n");
|
|
311
|
+
}
|
|
312
|
+
} else {
|
|
313
|
+
/* For other block types, prefix with > */
|
|
314
|
+
buffer_append_str(buf, "> ");
|
|
315
|
+
serialize_block(buf, child, dialect, indent_level);
|
|
316
|
+
/* Remove trailing newlines and add appropriate spacing */
|
|
317
|
+
if (cmark_node_next(child)) {
|
|
318
|
+
/* Ensure single newline between blockquote items */
|
|
319
|
+
if (buf->len > 0 && buf->buf[buf->len - 1] == '\n') {
|
|
320
|
+
buf->len--; /* Remove one newline */
|
|
321
|
+
buf->buf[buf->len] = '\0';
|
|
322
|
+
}
|
|
323
|
+
buffer_append_str(buf, "\n");
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
break;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
case CMARK_NODE_LIST: {
|
|
331
|
+
cmark_list_type list_type = cmark_node_get_list_type(node);
|
|
332
|
+
int item_index = 0;
|
|
333
|
+
for (cmark_node *item = cmark_node_first_child(node); item; item = cmark_node_next(item), item_index++) {
|
|
334
|
+
if (list_type == CMARK_ORDERED_LIST) {
|
|
335
|
+
int start = cmark_node_get_list_start(node);
|
|
336
|
+
char num[32];
|
|
337
|
+
snprintf(num, sizeof(num), "%d", start + item_index);
|
|
338
|
+
buffer_append_str(buf, num);
|
|
339
|
+
buffer_append_str(buf, ". ");
|
|
340
|
+
} else {
|
|
341
|
+
buffer_append_str(buf, "- ");
|
|
342
|
+
}
|
|
343
|
+
serialize_block(buf, item, dialect, indent_level);
|
|
344
|
+
}
|
|
345
|
+
buffer_append_str(buf, "\n");
|
|
346
|
+
break;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
case CMARK_NODE_ITEM:
|
|
350
|
+
for (cmark_node *child = cmark_node_first_child(node); child; child = cmark_node_next(child)) {
|
|
351
|
+
serialize_block(buf, child, dialect, indent_level);
|
|
352
|
+
}
|
|
353
|
+
break;
|
|
354
|
+
|
|
355
|
+
case CMARK_NODE_THEMATIC_BREAK:
|
|
356
|
+
buffer_append_str(buf, "---\n\n");
|
|
357
|
+
break;
|
|
358
|
+
|
|
359
|
+
case CMARK_NODE_HTML_BLOCK: {
|
|
360
|
+
const char *literal = cmark_node_get_literal(node);
|
|
361
|
+
if (literal) {
|
|
362
|
+
/* Check if this is a TOC marker HTML comment */
|
|
363
|
+
if (dialect == APEX_MD_DIALECT_MMD) {
|
|
364
|
+
const char *toc_start = strstr(literal, "<!--TOC");
|
|
365
|
+
if (toc_start) {
|
|
366
|
+
/* For MMD, convert HTML comment TOC marker to {{TOC}} format */
|
|
367
|
+
/* Always output just {{TOC}} (no parameters) for MMD compatibility */
|
|
368
|
+
buffer_append_str(buf, "{{TOC}}\n\n");
|
|
369
|
+
break;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
/* For other dialects or non-TOC HTML, output as-is */
|
|
373
|
+
buffer_append_str(buf, literal);
|
|
374
|
+
buffer_append_str(buf, "\n\n");
|
|
375
|
+
}
|
|
376
|
+
break;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
default:
|
|
380
|
+
/* For unknown types, serialize children */
|
|
381
|
+
for (cmark_node *child = cmark_node_first_child(node); child; child = cmark_node_next(child)) {
|
|
382
|
+
serialize_block(buf, child, dialect, indent_level);
|
|
383
|
+
}
|
|
384
|
+
break;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
char *apex_cmark_to_markdown(cmark_node *document,
|
|
389
|
+
const apex_options *options,
|
|
390
|
+
apex_markdown_dialect_t dialect) {
|
|
391
|
+
(void)options; /* Reserved for future use */
|
|
392
|
+
if (!document || cmark_node_get_type(document) != CMARK_NODE_DOCUMENT) {
|
|
393
|
+
return NULL;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
markdown_buffer buf;
|
|
397
|
+
buffer_init(&buf);
|
|
398
|
+
|
|
399
|
+
serialize_block(&buf, document, dialect, 0);
|
|
400
|
+
|
|
401
|
+
if (buf.buf) {
|
|
402
|
+
return buf.buf;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/* Fallback: return empty string */
|
|
406
|
+
char *empty = malloc(1);
|
|
407
|
+
if (empty) empty[0] = '\0';
|
|
408
|
+
return empty;
|
|
409
|
+
}
|