apex-ruby 1.0.6 → 1.0.7
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,1130 @@
|
|
|
1
|
+
#include "ast_json.h"
|
|
2
|
+
|
|
3
|
+
#include <stdlib.h>
|
|
4
|
+
#include <string.h>
|
|
5
|
+
#include <stdio.h>
|
|
6
|
+
#include <ctype.h>
|
|
7
|
+
|
|
8
|
+
/* ------------------------------------------------------------------------- */
|
|
9
|
+
/* Simple JSON string builder */
|
|
10
|
+
/* ------------------------------------------------------------------------- */
|
|
11
|
+
|
|
12
|
+
typedef struct {
|
|
13
|
+
char *data;
|
|
14
|
+
size_t len;
|
|
15
|
+
size_t cap;
|
|
16
|
+
} apex_json_buf;
|
|
17
|
+
|
|
18
|
+
static int apex_json_buf_init(apex_json_buf *b) {
|
|
19
|
+
b->cap = 4096;
|
|
20
|
+
b->len = 0;
|
|
21
|
+
b->data = (char *)malloc(b->cap);
|
|
22
|
+
if (!b->data) return 0;
|
|
23
|
+
b->data[0] = '\0';
|
|
24
|
+
return 1;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
static int apex_json_buf_ensure(apex_json_buf *b, size_t extra) {
|
|
28
|
+
if (b->len + extra + 1 <= b->cap) return 1;
|
|
29
|
+
size_t new_cap = b->cap * 2;
|
|
30
|
+
while (new_cap < b->len + extra + 1) {
|
|
31
|
+
new_cap *= 2;
|
|
32
|
+
}
|
|
33
|
+
char *nb = (char *)realloc(b->data, new_cap);
|
|
34
|
+
if (!nb) return 0;
|
|
35
|
+
b->data = nb;
|
|
36
|
+
b->cap = new_cap;
|
|
37
|
+
return 1;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
static int apex_json_buf_append(apex_json_buf *b, const char *s) {
|
|
41
|
+
if (!s) return 1;
|
|
42
|
+
size_t slen = strlen(s);
|
|
43
|
+
if (!apex_json_buf_ensure(b, slen)) return 0;
|
|
44
|
+
memcpy(b->data + b->len, s, slen);
|
|
45
|
+
b->len += slen;
|
|
46
|
+
b->data[b->len] = '\0';
|
|
47
|
+
return 1;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/* ------------------------------------------------------------------------- */
|
|
51
|
+
/* JSON string escaping */
|
|
52
|
+
/* ------------------------------------------------------------------------- */
|
|
53
|
+
|
|
54
|
+
static char *apex_json_escape_local(const char *text) {
|
|
55
|
+
if (!text) return NULL;
|
|
56
|
+
size_t len = strlen(text);
|
|
57
|
+
size_t cap = len * 6 + 1;
|
|
58
|
+
char *out = (char *)malloc(cap);
|
|
59
|
+
if (!out) return NULL;
|
|
60
|
+
|
|
61
|
+
char *w = out;
|
|
62
|
+
for (size_t i = 0; i < len; i++) {
|
|
63
|
+
unsigned char c = (unsigned char)text[i];
|
|
64
|
+
switch (c) {
|
|
65
|
+
case '\\': *w++ = '\\'; *w++ = '\\'; break;
|
|
66
|
+
case '"': *w++ = '\\'; *w++ = '"'; break;
|
|
67
|
+
case '\n': *w++ = '\\'; *w++ = 'n'; break;
|
|
68
|
+
case '\r': *w++ = '\\'; *w++ = 'r'; break;
|
|
69
|
+
case '\t': *w++ = '\\'; *w++ = 't'; break;
|
|
70
|
+
default:
|
|
71
|
+
if (c < 0x20) {
|
|
72
|
+
int written = snprintf(w, cap - (size_t)(w - out), "\\u%04X", c);
|
|
73
|
+
if (written <= 0 || (size_t)written >= cap - (size_t)(w - out)) {
|
|
74
|
+
free(out);
|
|
75
|
+
return NULL;
|
|
76
|
+
}
|
|
77
|
+
w += written;
|
|
78
|
+
} else {
|
|
79
|
+
*w++ = (char)c;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
*w = '\0';
|
|
84
|
+
return out;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
static int apex_json_buf_append_escaped_string(apex_json_buf *b,
|
|
88
|
+
const char *text) {
|
|
89
|
+
char *escaped = apex_json_escape_local(text ? text : "");
|
|
90
|
+
if (!escaped) return 0;
|
|
91
|
+
int ok = apex_json_buf_append(b, "\"") &&
|
|
92
|
+
apex_json_buf_append(b, escaped) &&
|
|
93
|
+
apex_json_buf_append(b, "\"");
|
|
94
|
+
free(escaped);
|
|
95
|
+
return ok;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/* ------------------------------------------------------------------------- */
|
|
99
|
+
/* Pandoc AST serialization helpers */
|
|
100
|
+
/* ------------------------------------------------------------------------- */
|
|
101
|
+
|
|
102
|
+
static int write_inlines(apex_json_buf *b, cmark_node *first_inline);
|
|
103
|
+
static int write_blocks(apex_json_buf *b, cmark_node *block);
|
|
104
|
+
|
|
105
|
+
/* Write a Pandoc Attr triple: [ id, [classes], [[k,v], ...] ] */
|
|
106
|
+
static int write_pandoc_attr_empty(apex_json_buf *b) {
|
|
107
|
+
return apex_json_buf_append(b, "[\"\",[],[]]");
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
static int write_inline(apex_json_buf *b, cmark_node *node) {
|
|
111
|
+
if (!node) return 1;
|
|
112
|
+
cmark_node_type t = cmark_node_get_type(node);
|
|
113
|
+
|
|
114
|
+
switch (t) {
|
|
115
|
+
case CMARK_NODE_TEXT: {
|
|
116
|
+
if (!apex_json_buf_append(b, "{\"t\":\"Str\",\"c\":")) return 0;
|
|
117
|
+
if (!apex_json_buf_append_escaped_string(b, cmark_node_get_literal(node))) return 0;
|
|
118
|
+
if (!apex_json_buf_append(b, "}")) return 0;
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
121
|
+
case CMARK_NODE_SOFTBREAK: {
|
|
122
|
+
if (!apex_json_buf_append(b, "{\"t\":\"SoftBreak\",\"c\":[]}")) return 0;
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
case CMARK_NODE_LINEBREAK: {
|
|
126
|
+
if (!apex_json_buf_append(b, "{\"t\":\"LineBreak\",\"c\":[]}")) return 0;
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
case CMARK_NODE_CODE: {
|
|
130
|
+
const char *lit = cmark_node_get_literal(node);
|
|
131
|
+
if (!apex_json_buf_append(b, "{\"t\":\"Code\",\"c\":[")) return 0;
|
|
132
|
+
if (!write_pandoc_attr_empty(b)) return 0;
|
|
133
|
+
if (!apex_json_buf_append(b, ",")) return 0;
|
|
134
|
+
if (!apex_json_buf_append_escaped_string(b, lit ? lit : "")) return 0;
|
|
135
|
+
if (!apex_json_buf_append(b, "]}")) return 0;
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
case CMARK_NODE_EMPH: {
|
|
139
|
+
if (!apex_json_buf_append(b, "{\"t\":\"Emph\",\"c\":[")) return 0;
|
|
140
|
+
if (!write_inlines(b, cmark_node_first_child(node))) return 0;
|
|
141
|
+
if (!apex_json_buf_append(b, "]}")) return 0;
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
case CMARK_NODE_STRONG: {
|
|
145
|
+
if (!apex_json_buf_append(b, "{\"t\":\"Strong\",\"c\":[")) return 0;
|
|
146
|
+
if (!write_inlines(b, cmark_node_first_child(node))) return 0;
|
|
147
|
+
if (!apex_json_buf_append(b, "]}")) return 0;
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
case CMARK_NODE_LINK: {
|
|
151
|
+
const char *url = cmark_node_get_url(node);
|
|
152
|
+
const char *title = cmark_node_get_title(node);
|
|
153
|
+
if (!apex_json_buf_append(b, "{\"t\":\"Link\",\"c\":[")) return 0;
|
|
154
|
+
if (!write_pandoc_attr_empty(b)) return 0;
|
|
155
|
+
if (!apex_json_buf_append(b, ",")) return 0;
|
|
156
|
+
if (!write_inlines(b, cmark_node_first_child(node))) return 0;
|
|
157
|
+
if (!apex_json_buf_append(b, ",[")) return 0;
|
|
158
|
+
if (!apex_json_buf_append_escaped_string(b, url ? url : "")) return 0;
|
|
159
|
+
if (!apex_json_buf_append(b, ",")) return 0;
|
|
160
|
+
if (!apex_json_buf_append_escaped_string(b, title ? title : "")) return 0;
|
|
161
|
+
if (!apex_json_buf_append(b, "]]}")) return 0;
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
164
|
+
case CMARK_NODE_IMAGE: {
|
|
165
|
+
const char *url = cmark_node_get_url(node);
|
|
166
|
+
const char *title = cmark_node_get_title(node);
|
|
167
|
+
if (!apex_json_buf_append(b, "{\"t\":\"Image\",\"c\":[")) return 0;
|
|
168
|
+
if (!write_pandoc_attr_empty(b)) return 0;
|
|
169
|
+
if (!apex_json_buf_append(b, ",")) return 0;
|
|
170
|
+
if (!write_inlines(b, cmark_node_first_child(node))) return 0;
|
|
171
|
+
if (!apex_json_buf_append(b, ",[")) return 0;
|
|
172
|
+
if (!apex_json_buf_append_escaped_string(b, url ? url : "")) return 0;
|
|
173
|
+
if (!apex_json_buf_append(b, ",")) return 0;
|
|
174
|
+
if (!apex_json_buf_append_escaped_string(b, title ? title : "")) return 0;
|
|
175
|
+
if (!apex_json_buf_append(b, "]]}")) return 0;
|
|
176
|
+
break;
|
|
177
|
+
}
|
|
178
|
+
case CMARK_NODE_HTML_INLINE: {
|
|
179
|
+
const char *lit = cmark_node_get_literal(node);
|
|
180
|
+
if (!apex_json_buf_append(b, "{\"t\":\"RawInline\",\"c\":[\"html\",")) return 0;
|
|
181
|
+
if (!apex_json_buf_append_escaped_string(b, lit ? lit : "")) return 0;
|
|
182
|
+
if (!apex_json_buf_append(b, "]}")) return 0;
|
|
183
|
+
break;
|
|
184
|
+
}
|
|
185
|
+
default: {
|
|
186
|
+
/* Fallback: treat as plain string of its literal (if any) */
|
|
187
|
+
const char *lit = cmark_node_get_literal(node);
|
|
188
|
+
if (!lit) lit = "";
|
|
189
|
+
if (!apex_json_buf_append(b, "{\"t\":\"Str\",\"c\":")) return 0;
|
|
190
|
+
if (!apex_json_buf_append_escaped_string(b, lit)) return 0;
|
|
191
|
+
if (!apex_json_buf_append(b, "}")) return 0;
|
|
192
|
+
break;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return 1;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
static int write_inlines(apex_json_buf *b, cmark_node *first_inline) {
|
|
200
|
+
if (!apex_json_buf_append(b, "[")) return 0;
|
|
201
|
+
cmark_node *cur = first_inline;
|
|
202
|
+
int first = 1;
|
|
203
|
+
while (cur) {
|
|
204
|
+
if (!first) {
|
|
205
|
+
if (!apex_json_buf_append(b, ",")) return 0;
|
|
206
|
+
}
|
|
207
|
+
if (!write_inline(b, cur)) return 0;
|
|
208
|
+
first = 0;
|
|
209
|
+
cur = cmark_node_next(cur);
|
|
210
|
+
}
|
|
211
|
+
if (!apex_json_buf_append(b, "]")) return 0;
|
|
212
|
+
return 1;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
static int write_block(apex_json_buf *b, cmark_node *node) {
|
|
216
|
+
if (!node) return 1;
|
|
217
|
+
cmark_node_type t = cmark_node_get_type(node);
|
|
218
|
+
|
|
219
|
+
switch (t) {
|
|
220
|
+
case CMARK_NODE_PARAGRAPH: {
|
|
221
|
+
if (!apex_json_buf_append(b, "{\"t\":\"Para\",\"c\":")) return 0;
|
|
222
|
+
if (!write_inlines(b, cmark_node_first_child(node))) return 0;
|
|
223
|
+
if (!apex_json_buf_append(b, "}")) return 0;
|
|
224
|
+
break;
|
|
225
|
+
}
|
|
226
|
+
case CMARK_NODE_HEADING: {
|
|
227
|
+
int level = cmark_node_get_heading_level(node);
|
|
228
|
+
if (level <= 0) level = 1;
|
|
229
|
+
if (!apex_json_buf_append(b, "{\"t\":\"Header\",\"c\":[")) return 0;
|
|
230
|
+
char buf[32];
|
|
231
|
+
snprintf(buf, sizeof(buf), "%d", level);
|
|
232
|
+
if (!apex_json_buf_append(b, buf)) return 0;
|
|
233
|
+
if (!apex_json_buf_append(b, ",")) return 0;
|
|
234
|
+
if (!write_pandoc_attr_empty(b)) return 0;
|
|
235
|
+
if (!apex_json_buf_append(b, ",")) return 0;
|
|
236
|
+
if (!write_inlines(b, cmark_node_first_child(node))) return 0;
|
|
237
|
+
if (!apex_json_buf_append(b, "]}")) return 0;
|
|
238
|
+
break;
|
|
239
|
+
}
|
|
240
|
+
case CMARK_NODE_THEMATIC_BREAK: {
|
|
241
|
+
if (!apex_json_buf_append(b, "{\"t\":\"HorizontalRule\",\"c\":[]}")) return 0;
|
|
242
|
+
break;
|
|
243
|
+
}
|
|
244
|
+
case CMARK_NODE_HTML_BLOCK: {
|
|
245
|
+
const char *lit = cmark_node_get_literal(node);
|
|
246
|
+
if (!apex_json_buf_append(b, "{\"t\":\"RawBlock\",\"c\":[\"html\",")) return 0;
|
|
247
|
+
if (!apex_json_buf_append_escaped_string(b, lit ? lit : "")) return 0;
|
|
248
|
+
if (!apex_json_buf_append(b, "]}")) return 0;
|
|
249
|
+
break;
|
|
250
|
+
}
|
|
251
|
+
case CMARK_NODE_CODE_BLOCK: {
|
|
252
|
+
const char *lit = cmark_node_get_literal(node);
|
|
253
|
+
const char *lang = cmark_node_get_fence_info(node);
|
|
254
|
+
size_t lit_len = lit ? strlen(lit) : 0;
|
|
255
|
+
/* Strip single trailing newline so JSON does not contain \\n (avoids decoder issues) */
|
|
256
|
+
if (lit_len > 0 && lit[lit_len - 1] == '\n') {
|
|
257
|
+
lit_len--;
|
|
258
|
+
}
|
|
259
|
+
if (!apex_json_buf_append(b, "{\"t\":\"CodeBlock\",\"c\":[[")) return 0; /* c = [Attr, content]; Attr = [id, classes, keyvals]; parser expects [[ */
|
|
260
|
+
if (!apex_json_buf_append_escaped_string(b, "")) return 0; /* id (no extra "[" so attr is [id,classes,keyvals] not [[...) */
|
|
261
|
+
if (!apex_json_buf_append(b, ",[")) return 0; /* classes */
|
|
262
|
+
if (lang && *lang) {
|
|
263
|
+
if (!apex_json_buf_append_escaped_string(b, lang)) return 0;
|
|
264
|
+
}
|
|
265
|
+
/* Close Attr (keyvals then attr) with ]], then comma before content */
|
|
266
|
+
if (lang && strcmp(lang, "inc") == 0) {
|
|
267
|
+
if (!apex_json_buf_append(b, "]\x2c[[\"inc\",\"yes\"]]],")) return 0;
|
|
268
|
+
} else {
|
|
269
|
+
if (!apex_json_buf_append(b, "],[]]],")) return 0;
|
|
270
|
+
}
|
|
271
|
+
if (lit_len > 0) {
|
|
272
|
+
char *lit_copy = (char *)malloc(lit_len + 1);
|
|
273
|
+
if (lit_copy) {
|
|
274
|
+
memcpy(lit_copy, lit, lit_len);
|
|
275
|
+
lit_copy[lit_len] = '\0';
|
|
276
|
+
if (!apex_json_buf_append_escaped_string(b, lit_copy)) { free(lit_copy); return 0; }
|
|
277
|
+
free(lit_copy);
|
|
278
|
+
} else if (!apex_json_buf_append_escaped_string(b, "")) {
|
|
279
|
+
return 0;
|
|
280
|
+
}
|
|
281
|
+
} else if (!apex_json_buf_append_escaped_string(b, "")) {
|
|
282
|
+
return 0;
|
|
283
|
+
}
|
|
284
|
+
if (!apex_json_buf_append(b, "]}")) return 0;
|
|
285
|
+
break;
|
|
286
|
+
}
|
|
287
|
+
case CMARK_NODE_LIST: {
|
|
288
|
+
cmark_list_type lt = cmark_node_get_list_type(node);
|
|
289
|
+
int is_ordered = (lt == CMARK_ORDERED_LIST);
|
|
290
|
+
if (is_ordered) {
|
|
291
|
+
if (!apex_json_buf_append(b, "{\"t\":\"OrderedList\",\"c\":[")) return 0;
|
|
292
|
+
/* Simple default list attributes: [start, style, delim] */
|
|
293
|
+
int start = cmark_node_get_list_start(node);
|
|
294
|
+
if (start <= 0) start = 1;
|
|
295
|
+
char buf[32];
|
|
296
|
+
snprintf(buf, sizeof(buf), "%d", start);
|
|
297
|
+
if (!apex_json_buf_append(b, "[")) return 0;
|
|
298
|
+
if (!apex_json_buf_append(b, buf)) return 0;
|
|
299
|
+
if (!apex_json_buf_append(b, ",\"Decimal\",\"Period\"],[")) return 0;
|
|
300
|
+
} else {
|
|
301
|
+
if (!apex_json_buf_append(b, "{\"t\":\"BulletList\",\"c\":[")) return 0;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/* List items: each item is a list of blocks */
|
|
305
|
+
cmark_node *item = cmark_node_first_child(node);
|
|
306
|
+
int first_item = 1;
|
|
307
|
+
while (item) {
|
|
308
|
+
if (!first_item) {
|
|
309
|
+
if (!apex_json_buf_append(b, ",")) return 0;
|
|
310
|
+
}
|
|
311
|
+
if (!apex_json_buf_append(b, "[")) return 0;
|
|
312
|
+
cmark_node *child = cmark_node_first_child(item);
|
|
313
|
+
int first_block = 1;
|
|
314
|
+
while (child) {
|
|
315
|
+
if (!first_block) {
|
|
316
|
+
if (!apex_json_buf_append(b, ",")) return 0;
|
|
317
|
+
}
|
|
318
|
+
if (!write_block(b, child)) return 0;
|
|
319
|
+
first_block = 0;
|
|
320
|
+
child = cmark_node_next(child);
|
|
321
|
+
}
|
|
322
|
+
if (!apex_json_buf_append(b, "]")) return 0;
|
|
323
|
+
first_item = 0;
|
|
324
|
+
item = cmark_node_next(item);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if (!apex_json_buf_append(b, "]}")) return 0;
|
|
328
|
+
break;
|
|
329
|
+
}
|
|
330
|
+
case CMARK_NODE_BLOCK_QUOTE: {
|
|
331
|
+
if (!apex_json_buf_append(b, "{\"t\":\"BlockQuote\",\"c\":[")) return 0;
|
|
332
|
+
cmark_node *child = cmark_node_first_child(node);
|
|
333
|
+
int first_block = 1;
|
|
334
|
+
while (child) {
|
|
335
|
+
if (!first_block) {
|
|
336
|
+
if (!apex_json_buf_append(b, ",")) return 0;
|
|
337
|
+
}
|
|
338
|
+
if (!write_block(b, child)) return 0;
|
|
339
|
+
first_block = 0;
|
|
340
|
+
child = cmark_node_next(child);
|
|
341
|
+
}
|
|
342
|
+
if (!apex_json_buf_append(b, "]}")) return 0;
|
|
343
|
+
break;
|
|
344
|
+
}
|
|
345
|
+
default: {
|
|
346
|
+
/* Fallback: render paragraph of plain text from this node's literal (if any) */
|
|
347
|
+
const char *lit = cmark_node_get_literal(node);
|
|
348
|
+
if (!apex_json_buf_append(b, "{\"t\":\"Para\",\"c\":[")) return 0;
|
|
349
|
+
if (!apex_json_buf_append(b, "{\"t\":\"Str\",\"c\":")) return 0;
|
|
350
|
+
if (!apex_json_buf_append_escaped_string(b, lit ? lit : "")) return 0;
|
|
351
|
+
if (!apex_json_buf_append(b, "}]}")) return 0;
|
|
352
|
+
break;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
return 1;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
static int write_blocks(apex_json_buf *b, cmark_node *block) {
|
|
360
|
+
int first = 1;
|
|
361
|
+
cmark_node *cur = block;
|
|
362
|
+
while (cur) {
|
|
363
|
+
if (!first) {
|
|
364
|
+
if (!apex_json_buf_append(b, ",")) return 0;
|
|
365
|
+
}
|
|
366
|
+
if (!write_block(b, cur)) return 0;
|
|
367
|
+
first = 0;
|
|
368
|
+
cur = cmark_node_next(cur);
|
|
369
|
+
}
|
|
370
|
+
return 1;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/* ------------------------------------------------------------------------- */
|
|
374
|
+
/* Public: cmark -> Pandoc JSON */
|
|
375
|
+
/* ------------------------------------------------------------------------- */
|
|
376
|
+
|
|
377
|
+
char *apex_cmark_to_pandoc_json(cmark_node *document,
|
|
378
|
+
const apex_options *options) {
|
|
379
|
+
(void)options; /* reserved for future metadata mapping */
|
|
380
|
+
if (!document || cmark_node_get_type(document) != CMARK_NODE_DOCUMENT) {
|
|
381
|
+
return NULL;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
apex_json_buf b;
|
|
385
|
+
if (!apex_json_buf_init(&b)) {
|
|
386
|
+
return NULL;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/* Top-level Pandoc object. We use a fixed pandoc-api-version that is
|
|
390
|
+
* compatible with modern Pandoc filters; the exact version is not
|
|
391
|
+
* critical for most filters.
|
|
392
|
+
*/
|
|
393
|
+
if (!apex_json_buf_append(&b, "{")) goto error;
|
|
394
|
+
if (!apex_json_buf_append(&b, "\"pandoc-api-version\":[1,23,1],\"meta\":{},\"blocks\":[")) goto error;
|
|
395
|
+
|
|
396
|
+
if (!write_blocks(&b, cmark_node_first_child(document))) goto error;
|
|
397
|
+
|
|
398
|
+
if (!apex_json_buf_append(&b, "]}")) goto error;
|
|
399
|
+
|
|
400
|
+
return b.data;
|
|
401
|
+
|
|
402
|
+
error:
|
|
403
|
+
free(b.data);
|
|
404
|
+
return NULL;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/* ------------------------------------------------------------------------- */
|
|
408
|
+
/* Minimal JSON parser for Pandoc subset (JSON -> cmark) */
|
|
409
|
+
/* ------------------------------------------------------------------------- */
|
|
410
|
+
|
|
411
|
+
typedef struct {
|
|
412
|
+
const char *s;
|
|
413
|
+
} json_cursor;
|
|
414
|
+
|
|
415
|
+
static void json_skip_ws(json_cursor *cur) {
|
|
416
|
+
while (*cur->s && isspace((unsigned char)*cur->s)) {
|
|
417
|
+
cur->s++;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
static int json_match_char(json_cursor *cur, char ch) {
|
|
422
|
+
json_skip_ws(cur);
|
|
423
|
+
if (*cur->s != ch) return 0;
|
|
424
|
+
cur->s++;
|
|
425
|
+
return 1;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
static char *json_parse_string(json_cursor *cur) {
|
|
429
|
+
json_skip_ws(cur);
|
|
430
|
+
if (*cur->s != '"') return NULL;
|
|
431
|
+
cur->s++; /* skip quote */
|
|
432
|
+
const char *start = cur->s;
|
|
433
|
+
size_t cap = 64;
|
|
434
|
+
size_t len = 0;
|
|
435
|
+
char *out = (char *)malloc(cap);
|
|
436
|
+
if (!out) return NULL;
|
|
437
|
+
|
|
438
|
+
while (*cur->s && *cur->s != '"') {
|
|
439
|
+
unsigned char c = (unsigned char)*cur->s;
|
|
440
|
+
if (c == '\\') {
|
|
441
|
+
cur->s++;
|
|
442
|
+
c = (unsigned char)*cur->s;
|
|
443
|
+
if (!c) { free(out); return NULL; }
|
|
444
|
+
switch (c) {
|
|
445
|
+
case '"': c = '"'; break;
|
|
446
|
+
case '\\': c = '\\'; break;
|
|
447
|
+
case '/': c = '/'; break;
|
|
448
|
+
case 'b': c = '\b'; break;
|
|
449
|
+
case 'f': c = '\f'; break;
|
|
450
|
+
case 'n': c = '\n'; break;
|
|
451
|
+
case 'r': c = '\r'; break;
|
|
452
|
+
case 't': c = '\t'; break;
|
|
453
|
+
case 'u':
|
|
454
|
+
/* For simplicity, skip \uXXXX and store as '?' */
|
|
455
|
+
cur->s++;
|
|
456
|
+
for (int i = 0; i < 4 && *cur->s; i++) {
|
|
457
|
+
cur->s++;
|
|
458
|
+
}
|
|
459
|
+
c = '?';
|
|
460
|
+
break;
|
|
461
|
+
default:
|
|
462
|
+
/* Unknown escape; keep as-is */
|
|
463
|
+
break;
|
|
464
|
+
}
|
|
465
|
+
/* Advance past the escaped character (for \u we already advanced in the case) */
|
|
466
|
+
if (c != '?')
|
|
467
|
+
cur->s++;
|
|
468
|
+
} else {
|
|
469
|
+
cur->s++;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
if (len + 1 >= cap) {
|
|
473
|
+
cap *= 2;
|
|
474
|
+
char *nb = (char *)realloc(out, cap);
|
|
475
|
+
if (!nb) {
|
|
476
|
+
free(out);
|
|
477
|
+
return NULL;
|
|
478
|
+
}
|
|
479
|
+
out = nb;
|
|
480
|
+
}
|
|
481
|
+
out[len++] = (char)c;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
if (*cur->s != '"') {
|
|
485
|
+
free(out);
|
|
486
|
+
return NULL;
|
|
487
|
+
}
|
|
488
|
+
cur->s++; /* closing quote */
|
|
489
|
+
out[len] = '\0';
|
|
490
|
+
(void)start;
|
|
491
|
+
return out;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
static long json_parse_int(json_cursor *cur) {
|
|
495
|
+
json_skip_ws(cur);
|
|
496
|
+
int neg = 0;
|
|
497
|
+
if (*cur->s == '-') {
|
|
498
|
+
neg = 1;
|
|
499
|
+
cur->s++;
|
|
500
|
+
}
|
|
501
|
+
long v = 0;
|
|
502
|
+
while (*cur->s && isdigit((unsigned char)*cur->s)) {
|
|
503
|
+
v = v * 10 + (*cur->s - '0');
|
|
504
|
+
cur->s++;
|
|
505
|
+
}
|
|
506
|
+
return neg ? -v : v;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
/* Forward declarations for recursive descent on the Pandoc subset */
|
|
510
|
+
static cmark_node *parse_blocks_array(json_cursor *cur);
|
|
511
|
+
static cmark_node *parse_block_object(json_cursor *cur);
|
|
512
|
+
static int json_skip_value(json_cursor *cur);
|
|
513
|
+
|
|
514
|
+
/* Skip arbitrary JSON value (used for meta and fields we don't care about) */
|
|
515
|
+
static int json_skip_value(json_cursor *cur) {
|
|
516
|
+
json_skip_ws(cur);
|
|
517
|
+
char c = *cur->s;
|
|
518
|
+
if (!c) return 0;
|
|
519
|
+
if (c == '"') {
|
|
520
|
+
char *tmp = json_parse_string(cur);
|
|
521
|
+
if (!tmp) return 0;
|
|
522
|
+
free(tmp);
|
|
523
|
+
return 1;
|
|
524
|
+
}
|
|
525
|
+
if (c == '{') {
|
|
526
|
+
cur->s++;
|
|
527
|
+
for (;;) {
|
|
528
|
+
json_skip_ws(cur);
|
|
529
|
+
if (*cur->s == '}') {
|
|
530
|
+
cur->s++;
|
|
531
|
+
break;
|
|
532
|
+
}
|
|
533
|
+
/* key */
|
|
534
|
+
char *k = json_parse_string(cur);
|
|
535
|
+
if (!k) return 0;
|
|
536
|
+
free(k);
|
|
537
|
+
if (!json_match_char(cur, ':')) return 0;
|
|
538
|
+
if (!json_skip_value(cur)) return 0;
|
|
539
|
+
json_skip_ws(cur);
|
|
540
|
+
if (*cur->s == ',') {
|
|
541
|
+
cur->s++;
|
|
542
|
+
continue;
|
|
543
|
+
} else if (*cur->s == '}') {
|
|
544
|
+
continue;
|
|
545
|
+
} else {
|
|
546
|
+
break;
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
return 1;
|
|
550
|
+
}
|
|
551
|
+
if (c == '[') {
|
|
552
|
+
cur->s++;
|
|
553
|
+
for (;;) {
|
|
554
|
+
json_skip_ws(cur);
|
|
555
|
+
if (*cur->s == ']') {
|
|
556
|
+
cur->s++;
|
|
557
|
+
break;
|
|
558
|
+
}
|
|
559
|
+
if (!json_skip_value(cur)) return 0;
|
|
560
|
+
json_skip_ws(cur);
|
|
561
|
+
if (*cur->s == ',') {
|
|
562
|
+
cur->s++;
|
|
563
|
+
continue;
|
|
564
|
+
} else if (*cur->s == ']') {
|
|
565
|
+
continue;
|
|
566
|
+
} else {
|
|
567
|
+
break;
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
return 1;
|
|
571
|
+
}
|
|
572
|
+
/* number, true, false, null – skip simple token */
|
|
573
|
+
while (*cur->s && !isspace((unsigned char)*cur->s) &&
|
|
574
|
+
*cur->s != ',' && *cur->s != ']' && *cur->s != '}') {
|
|
575
|
+
cur->s++;
|
|
576
|
+
}
|
|
577
|
+
return 1;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
/* Parse a Pandoc Inline array into a linked list of cmark nodes attached
|
|
581
|
+
* under a temporary dummy parent; returns first child.
|
|
582
|
+
*/
|
|
583
|
+
static cmark_node *parse_inlines_array(json_cursor *cur) {
|
|
584
|
+
if (!json_match_char(cur, '[')) return NULL;
|
|
585
|
+
cmark_node *dummy = cmark_node_new(CMARK_NODE_PARAGRAPH);
|
|
586
|
+
if (!dummy) return NULL;
|
|
587
|
+
/* 'last' was previously used but is no longer needed; we intentionally
|
|
588
|
+
* omit it to avoid unused-variable warnings.
|
|
589
|
+
*/
|
|
590
|
+
|
|
591
|
+
json_skip_ws(cur);
|
|
592
|
+
if (*cur->s == ']') {
|
|
593
|
+
cur->s++;
|
|
594
|
+
cmark_node *first = cmark_node_first_child(dummy);
|
|
595
|
+
cmark_node_free(dummy);
|
|
596
|
+
return first;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
int ok = 1;
|
|
600
|
+
while (ok) {
|
|
601
|
+
json_skip_ws(cur);
|
|
602
|
+
if (*cur->s != '{') { ok = 0; break; }
|
|
603
|
+
cur->s++; /* object start */
|
|
604
|
+
|
|
605
|
+
/* Parse object with any key order (e.g. dkjson outputs "c" before "t") */
|
|
606
|
+
char *tag = NULL;
|
|
607
|
+
char *c_buf = NULL;
|
|
608
|
+
while (1) {
|
|
609
|
+
json_skip_ws(cur);
|
|
610
|
+
if (*cur->s == '}') {
|
|
611
|
+
cur->s++;
|
|
612
|
+
break;
|
|
613
|
+
}
|
|
614
|
+
char *key = json_parse_string(cur);
|
|
615
|
+
if (!key) { ok = 0; break; }
|
|
616
|
+
if (!json_match_char(cur, ':')) { free(key); ok = 0; break; }
|
|
617
|
+
if (strcmp(key, "t") == 0) {
|
|
618
|
+
tag = json_parse_string(cur);
|
|
619
|
+
free(key);
|
|
620
|
+
if (!tag) { ok = 0; break; }
|
|
621
|
+
} else if (strcmp(key, "c") == 0) {
|
|
622
|
+
json_skip_ws(cur);
|
|
623
|
+
const char *v_start = cur->s;
|
|
624
|
+
if (!json_skip_value(cur)) { free(key); ok = 0; break; }
|
|
625
|
+
size_t vlen = (size_t)(cur->s - v_start);
|
|
626
|
+
c_buf = (char *)malloc(vlen + 1);
|
|
627
|
+
if (!c_buf) { free(key); ok = 0; break; }
|
|
628
|
+
memcpy(c_buf, v_start, vlen);
|
|
629
|
+
c_buf[vlen] = '\0';
|
|
630
|
+
free(key);
|
|
631
|
+
} else {
|
|
632
|
+
if (!json_skip_value(cur)) { free(key); ok = 0; break; }
|
|
633
|
+
free(key);
|
|
634
|
+
}
|
|
635
|
+
json_skip_ws(cur);
|
|
636
|
+
if (*cur->s == ',') cur->s++;
|
|
637
|
+
}
|
|
638
|
+
if (!ok || !tag || !c_buf) {
|
|
639
|
+
free(tag);
|
|
640
|
+
free(c_buf);
|
|
641
|
+
ok = 0;
|
|
642
|
+
break;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
/* Parse "c" from buffer so we can dispatch by tag */
|
|
646
|
+
json_cursor ccur = { c_buf };
|
|
647
|
+
cmark_node *node = NULL;
|
|
648
|
+
|
|
649
|
+
if (strcmp(tag, "Str") == 0) {
|
|
650
|
+
char *txt = json_parse_string(&ccur);
|
|
651
|
+
if (!txt) { free(tag); free(c_buf); ok = 0; break; }
|
|
652
|
+
node = cmark_node_new(CMARK_NODE_TEXT);
|
|
653
|
+
if (!node) { free(tag); free(c_buf); free(txt); ok = 0; break; }
|
|
654
|
+
cmark_node_set_literal(node, txt);
|
|
655
|
+
free(txt);
|
|
656
|
+
} else if (strcmp(tag, "SoftBreak") == 0) {
|
|
657
|
+
node = cmark_node_new(CMARK_NODE_SOFTBREAK);
|
|
658
|
+
} else if (strcmp(tag, "LineBreak") == 0) {
|
|
659
|
+
node = cmark_node_new(CMARK_NODE_LINEBREAK);
|
|
660
|
+
} else if (strcmp(tag, "Emph") == 0 || strcmp(tag, "Strong") == 0) {
|
|
661
|
+
if (!json_match_char(&ccur, '[')) { free(tag); free(c_buf); ok = 0; break; }
|
|
662
|
+
cmark_node *wrapper = cmark_node_new(strcmp(tag, "Emph") == 0
|
|
663
|
+
? CMARK_NODE_EMPH
|
|
664
|
+
: CMARK_NODE_STRONG);
|
|
665
|
+
if (!wrapper) { free(tag); free(c_buf); ok = 0; break; }
|
|
666
|
+
json_skip_ws(&ccur);
|
|
667
|
+
if (*ccur.s != ']') {
|
|
668
|
+
cmark_node *child_first = parse_inlines_array(&ccur);
|
|
669
|
+
cmark_node *child = child_first;
|
|
670
|
+
while (child) {
|
|
671
|
+
cmark_node *next = cmark_node_next(child);
|
|
672
|
+
cmark_node_append_child(wrapper, child);
|
|
673
|
+
child = next;
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
node = wrapper;
|
|
677
|
+
} else if (strcmp(tag, "Code") == 0) {
|
|
678
|
+
if (!json_match_char(&ccur, '[')) { free(tag); free(c_buf); ok = 0; break; }
|
|
679
|
+
if (!json_skip_value(&ccur)) { free(tag); free(c_buf); ok = 0; break; }
|
|
680
|
+
if (!json_match_char(&ccur, ',')) { free(tag); free(c_buf); ok = 0; break; }
|
|
681
|
+
char *txt = json_parse_string(&ccur);
|
|
682
|
+
if (!txt) { free(tag); free(c_buf); ok = 0; break; }
|
|
683
|
+
node = cmark_node_new(CMARK_NODE_CODE);
|
|
684
|
+
if (!node) { free(tag); free(c_buf); free(txt); ok = 0; break; }
|
|
685
|
+
cmark_node_set_literal(node, txt);
|
|
686
|
+
free(txt);
|
|
687
|
+
} else if (strcmp(tag, "Span") == 0) {
|
|
688
|
+
if (!json_match_char(&ccur, '[')) { free(tag); free(c_buf); ok = 0; break; }
|
|
689
|
+
if (!json_skip_value(&ccur)) { free(tag); free(c_buf); ok = 0; break; }
|
|
690
|
+
if (!json_match_char(&ccur, ',')) { free(tag); free(c_buf); ok = 0; break; }
|
|
691
|
+
|
|
692
|
+
cmark_node *child_first = parse_inlines_array(&ccur);
|
|
693
|
+
|
|
694
|
+
cmark_node *child = child_first;
|
|
695
|
+
while (child) {
|
|
696
|
+
cmark_node *next = cmark_node_next(child);
|
|
697
|
+
if (!cmark_node_append_child(dummy, child)) {
|
|
698
|
+
cmark_node_free(child);
|
|
699
|
+
ok = 0;
|
|
700
|
+
break;
|
|
701
|
+
}
|
|
702
|
+
child = next;
|
|
703
|
+
}
|
|
704
|
+
node = NULL;
|
|
705
|
+
} else if (strcmp(tag, "Math") == 0) {
|
|
706
|
+
if (!json_match_char(&ccur, '[')) { free(tag); free(c_buf); ok = 0; break; }
|
|
707
|
+
if (!json_match_char(&ccur, '{')) { free(tag); free(c_buf); ok = 0; break; }
|
|
708
|
+
char *kind_key = json_parse_string(&ccur);
|
|
709
|
+
if (!kind_key) { free(tag); free(c_buf); ok = 0; break; }
|
|
710
|
+
if (!json_match_char(&ccur, ':')) { free(kind_key); free(tag); free(c_buf); ok = 0; break; }
|
|
711
|
+
char *kind = json_parse_string(&ccur);
|
|
712
|
+
free(kind_key);
|
|
713
|
+
if (!kind) { free(tag); free(c_buf); ok = 0; break; }
|
|
714
|
+
if (!json_match_char(&ccur, '}')) { free(kind); free(tag); free(c_buf); ok = 0; break; }
|
|
715
|
+
if (!json_match_char(&ccur, ',')) { free(kind); free(tag); free(c_buf); ok = 0; break; }
|
|
716
|
+
|
|
717
|
+
char *code = json_parse_string(&ccur);
|
|
718
|
+
if (!code) { free(kind); free(tag); free(c_buf); ok = 0; break; }
|
|
719
|
+
|
|
720
|
+
const char *delim = (strcmp(kind, "DisplayMath") == 0) ? "$$" : "$";
|
|
721
|
+
size_t dlen = strlen(delim);
|
|
722
|
+
size_t clen = strlen(code);
|
|
723
|
+
size_t wlen = dlen * 2 + clen + 1;
|
|
724
|
+
char *wrapped = (char *)malloc(wlen);
|
|
725
|
+
if (!wrapped) {
|
|
726
|
+
free(code);
|
|
727
|
+
free(kind);
|
|
728
|
+
free(tag);
|
|
729
|
+
free(c_buf);
|
|
730
|
+
ok = 0;
|
|
731
|
+
break;
|
|
732
|
+
}
|
|
733
|
+
snprintf(wrapped, wlen, "%s%s%s", delim, code, delim);
|
|
734
|
+
|
|
735
|
+
node = cmark_node_new(CMARK_NODE_HTML_INLINE);
|
|
736
|
+
if (!node) {
|
|
737
|
+
free(wrapped);
|
|
738
|
+
free(code);
|
|
739
|
+
free(kind);
|
|
740
|
+
free(tag);
|
|
741
|
+
free(c_buf);
|
|
742
|
+
ok = 0;
|
|
743
|
+
break;
|
|
744
|
+
}
|
|
745
|
+
cmark_node_set_literal(node, wrapped);
|
|
746
|
+
|
|
747
|
+
free(wrapped);
|
|
748
|
+
free(code);
|
|
749
|
+
free(kind);
|
|
750
|
+
} else if (strcmp(tag, "RawInline") == 0) {
|
|
751
|
+
if (!json_match_char(&ccur, '[')) { free(tag); free(c_buf); ok = 0; break; }
|
|
752
|
+
if (!json_skip_value(&ccur)) { free(tag); free(c_buf); ok = 0; break; }
|
|
753
|
+
if (!json_match_char(&ccur, ',')) { free(tag); free(c_buf); ok = 0; break; }
|
|
754
|
+
char *txt = json_parse_string(&ccur);
|
|
755
|
+
if (!txt) { free(tag); free(c_buf); ok = 0; break; }
|
|
756
|
+
node = cmark_node_new(CMARK_NODE_HTML_INLINE);
|
|
757
|
+
if (!node) { free(tag); free(c_buf); free(txt); ok = 0; break; }
|
|
758
|
+
cmark_node_set_literal(node, txt);
|
|
759
|
+
free(txt);
|
|
760
|
+
}
|
|
761
|
+
/* Unknown inline: node stays NULL, no node created */
|
|
762
|
+
|
|
763
|
+
free(tag);
|
|
764
|
+
free(c_buf);
|
|
765
|
+
/* Object closing '}' already consumed in the key-order loop */
|
|
766
|
+
|
|
767
|
+
if (node) {
|
|
768
|
+
if (!cmark_node_append_child(dummy, node)) {
|
|
769
|
+
cmark_node_free(node);
|
|
770
|
+
ok = 0;
|
|
771
|
+
break;
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
json_skip_ws(cur);
|
|
776
|
+
if (*cur->s == ',') {
|
|
777
|
+
cur->s++;
|
|
778
|
+
continue;
|
|
779
|
+
} else if (*cur->s == ']') {
|
|
780
|
+
break;
|
|
781
|
+
} else {
|
|
782
|
+
/* Unexpected character */
|
|
783
|
+
break;
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
/* Expect closing ']' */
|
|
788
|
+
if (!json_match_char(cur, ']')) {
|
|
789
|
+
cmark_node_free(dummy);
|
|
790
|
+
return NULL;
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
cmark_node *first = cmark_node_first_child(dummy);
|
|
794
|
+
if (first) {
|
|
795
|
+
/* Detach children without freeing */
|
|
796
|
+
cmark_node *child = first;
|
|
797
|
+
while (child) {
|
|
798
|
+
cmark_node *next = cmark_node_next(child);
|
|
799
|
+
cmark_node_unlink(child);
|
|
800
|
+
child = next;
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
cmark_node_free(dummy);
|
|
804
|
+
return first;
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
/* Parse a single Block object {"t": "...", "c": ...} (any key order, e.g. dkjson "c" before "t") */
|
|
808
|
+
static cmark_node *parse_block_object(json_cursor *cur) {
|
|
809
|
+
if (!json_match_char(cur, '{')) return NULL;
|
|
810
|
+
|
|
811
|
+
char *tag = NULL;
|
|
812
|
+
char *c_buf = NULL;
|
|
813
|
+
while (1) {
|
|
814
|
+
json_skip_ws(cur);
|
|
815
|
+
if (*cur->s == '}') {
|
|
816
|
+
/* Only leave the loop when we have both "t" and "c" (handles dkjson "c" before "t") */
|
|
817
|
+
if (tag && c_buf) {
|
|
818
|
+
cur->s++;
|
|
819
|
+
break;
|
|
820
|
+
}
|
|
821
|
+
free(tag);
|
|
822
|
+
free(c_buf);
|
|
823
|
+
return NULL;
|
|
824
|
+
}
|
|
825
|
+
char *key = json_parse_string(cur);
|
|
826
|
+
if (!key) return NULL;
|
|
827
|
+
if (!json_match_char(cur, ':')) { free(key); free(tag); free(c_buf); return NULL; }
|
|
828
|
+
if (strcmp(key, "t") == 0) {
|
|
829
|
+
tag = json_parse_string(cur);
|
|
830
|
+
free(key);
|
|
831
|
+
if (!tag) { free(c_buf); return NULL; }
|
|
832
|
+
} else if (strcmp(key, "c") == 0) {
|
|
833
|
+
json_skip_ws(cur);
|
|
834
|
+
const char *v_start = cur->s;
|
|
835
|
+
if (!json_skip_value(cur)) { free(key); free(tag); free(c_buf); return NULL; }
|
|
836
|
+
size_t vlen = (size_t)(cur->s - v_start);
|
|
837
|
+
c_buf = (char *)malloc(vlen + 1);
|
|
838
|
+
if (!c_buf) { free(key); free(tag); return NULL; }
|
|
839
|
+
memcpy(c_buf, v_start, vlen);
|
|
840
|
+
c_buf[vlen] = '\0';
|
|
841
|
+
free(key);
|
|
842
|
+
} else {
|
|
843
|
+
if (!json_skip_value(cur)) { free(key); free(tag); free(c_buf); return NULL; }
|
|
844
|
+
free(key);
|
|
845
|
+
}
|
|
846
|
+
json_skip_ws(cur);
|
|
847
|
+
if (*cur->s == ',') cur->s++;
|
|
848
|
+
}
|
|
849
|
+
if (!tag || !c_buf) {
|
|
850
|
+
free(tag);
|
|
851
|
+
free(c_buf);
|
|
852
|
+
return NULL;
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
json_cursor ccur = { c_buf };
|
|
856
|
+
cmark_node *node = NULL;
|
|
857
|
+
|
|
858
|
+
if (strcmp(tag, "Para") == 0) {
|
|
859
|
+
cmark_node *para = cmark_node_new(CMARK_NODE_PARAGRAPH);
|
|
860
|
+
if (!para) { free(tag); free(c_buf); return NULL; }
|
|
861
|
+
cmark_node *child_first = parse_inlines_array(&ccur);
|
|
862
|
+
cmark_node *child = child_first;
|
|
863
|
+
while (child) {
|
|
864
|
+
cmark_node *next = cmark_node_next(child);
|
|
865
|
+
cmark_node_append_child(para, child);
|
|
866
|
+
child = next;
|
|
867
|
+
}
|
|
868
|
+
node = para;
|
|
869
|
+
} else if (strcmp(tag, "Header") == 0) {
|
|
870
|
+
if (!json_match_char(&ccur, '[')) { free(tag); free(c_buf); return NULL; }
|
|
871
|
+
long level = json_parse_int(&ccur);
|
|
872
|
+
if (level <= 0) level = 1;
|
|
873
|
+
if (!json_match_char(&ccur, ',')) { free(tag); free(c_buf); return NULL; }
|
|
874
|
+
if (!json_skip_value(&ccur)) { free(tag); free(c_buf); return NULL; }
|
|
875
|
+
if (!json_match_char(&ccur, ',')) { free(tag); free(c_buf); return NULL; }
|
|
876
|
+
cmark_node *heading = cmark_node_new(CMARK_NODE_HEADING);
|
|
877
|
+
if (!heading) { free(tag); free(c_buf); return NULL; }
|
|
878
|
+
cmark_node_set_heading_level(heading, (int)level);
|
|
879
|
+
cmark_node *child_first = parse_inlines_array(&ccur);
|
|
880
|
+
cmark_node *child = child_first;
|
|
881
|
+
while (child) {
|
|
882
|
+
cmark_node *next = cmark_node_next(child);
|
|
883
|
+
cmark_node_append_child(heading, child);
|
|
884
|
+
child = next;
|
|
885
|
+
}
|
|
886
|
+
json_skip_ws(&ccur);
|
|
887
|
+
if (*ccur.s == ']')
|
|
888
|
+
ccur.s++;
|
|
889
|
+
node = heading;
|
|
890
|
+
} else if (strcmp(tag, "HorizontalRule") == 0) {
|
|
891
|
+
if (!json_skip_value(&ccur)) { free(tag); free(c_buf); return NULL; }
|
|
892
|
+
node = cmark_node_new(CMARK_NODE_THEMATIC_BREAK);
|
|
893
|
+
} else if (strcmp(tag, "RawBlock") == 0) {
|
|
894
|
+
json_skip_ws(&ccur);
|
|
895
|
+
if (!json_match_char(&ccur, '[')) { free(tag); free(c_buf); return NULL; }
|
|
896
|
+
if (!json_skip_value(&ccur)) { free(tag); free(c_buf); return NULL; }
|
|
897
|
+
if (!json_match_char(&ccur, ',')) { free(tag); free(c_buf); return NULL; }
|
|
898
|
+
char *txt = json_parse_string(&ccur);
|
|
899
|
+
if (!txt) { free(tag); free(c_buf); return NULL; }
|
|
900
|
+
if (!json_match_char(&ccur, ']')) { free(tag); free(c_buf); free(txt); return NULL; }
|
|
901
|
+
node = cmark_node_new(CMARK_NODE_HTML_BLOCK);
|
|
902
|
+
if (!node) { free(tag); free(c_buf); free(txt); return NULL; }
|
|
903
|
+
cmark_node_set_literal(node, txt);
|
|
904
|
+
free(txt);
|
|
905
|
+
} else if (strcmp(tag, "CodeBlock") == 0) {
|
|
906
|
+
if (!json_match_char(&ccur, '[')) { free(tag); free(c_buf); return NULL; }
|
|
907
|
+
if (!json_match_char(&ccur, '[')) { free(tag); free(c_buf); return NULL; }
|
|
908
|
+
char *id = json_parse_string(&ccur);
|
|
909
|
+
(void)id;
|
|
910
|
+
free(id);
|
|
911
|
+
if (!json_match_char(&ccur, ',')) { free(tag); free(c_buf); return NULL; }
|
|
912
|
+
if (!json_match_char(&ccur, '[')) { free(tag); free(c_buf); return NULL; }
|
|
913
|
+
char *lang = NULL;
|
|
914
|
+
json_skip_ws(&ccur);
|
|
915
|
+
if (*ccur.s != ']') {
|
|
916
|
+
lang = json_parse_string(&ccur);
|
|
917
|
+
}
|
|
918
|
+
if (!json_match_char(&ccur, ']')) { free(tag); free(c_buf); free(lang); return NULL; }
|
|
919
|
+
if (!json_match_char(&ccur, ',')) { free(tag); free(c_buf); free(lang); return NULL; }
|
|
920
|
+
if (!json_skip_value(&ccur)) { free(tag); free(c_buf); free(lang); return NULL; }
|
|
921
|
+
if (!json_match_char(&ccur, ']')) { free(tag); free(c_buf); free(lang); return NULL; }
|
|
922
|
+
if (!json_match_char(&ccur, ',')) { free(tag); free(c_buf); free(lang); return NULL; }
|
|
923
|
+
char *txt = json_parse_string(&ccur);
|
|
924
|
+
if (!txt) { free(tag); free(c_buf); free(lang); return NULL; }
|
|
925
|
+
node = cmark_node_new(CMARK_NODE_CODE_BLOCK);
|
|
926
|
+
if (!node) { free(tag); free(c_buf); free(lang); free(txt); return NULL; }
|
|
927
|
+
cmark_node_set_literal(node, txt);
|
|
928
|
+
if (lang && *lang) {
|
|
929
|
+
cmark_node_set_fence_info(node, lang);
|
|
930
|
+
}
|
|
931
|
+
free(txt);
|
|
932
|
+
free(lang);
|
|
933
|
+
} else if (strcmp(tag, "BulletList") == 0 || strcmp(tag, "OrderedList") == 0) {
|
|
934
|
+
int is_ordered = (strcmp(tag, "OrderedList") == 0);
|
|
935
|
+
if (is_ordered) {
|
|
936
|
+
if (!json_match_char(&ccur, '[')) { free(tag); free(c_buf); return NULL; }
|
|
937
|
+
long start = json_parse_int(&ccur);
|
|
938
|
+
if (start <= 0) start = 1;
|
|
939
|
+
if (!json_match_char(&ccur, ',')) { free(tag); free(c_buf); return NULL; }
|
|
940
|
+
if (!json_skip_value(&ccur)) { free(tag); free(c_buf); return NULL; }
|
|
941
|
+
if (!json_match_char(&ccur, ',')) { free(tag); free(c_buf); return NULL; }
|
|
942
|
+
if (!json_skip_value(&ccur)) { free(tag); free(c_buf); return NULL; }
|
|
943
|
+
if (!json_match_char(&ccur, ',')) { free(tag); free(c_buf); return NULL; }
|
|
944
|
+
} else {
|
|
945
|
+
if (!json_match_char(&ccur, '[')) { free(tag); free(c_buf); return NULL; }
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
cmark_node *list = cmark_node_new(CMARK_NODE_LIST);
|
|
949
|
+
if (!list) { free(tag); free(c_buf); return NULL; }
|
|
950
|
+
if (is_ordered) {
|
|
951
|
+
cmark_node_set_list_type(list, CMARK_ORDERED_LIST);
|
|
952
|
+
} else {
|
|
953
|
+
cmark_node_set_list_type(list, CMARK_BULLET_LIST);
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
if (!json_match_char(&ccur, '[')) { cmark_node_free(list); free(tag); free(c_buf); return NULL; }
|
|
957
|
+
json_skip_ws(&ccur);
|
|
958
|
+
while (*ccur.s != ']') {
|
|
959
|
+
if (!json_match_char(&ccur, '[')) { cmark_node_free(list); free(tag); free(c_buf); return NULL; }
|
|
960
|
+
cmark_node *item = cmark_node_new(CMARK_NODE_ITEM);
|
|
961
|
+
if (!item) { cmark_node_free(list); free(tag); free(c_buf); return NULL; }
|
|
962
|
+
cmark_node *blocks = parse_blocks_array(&ccur);
|
|
963
|
+
if (blocks) {
|
|
964
|
+
cmark_node *blk = cmark_node_first_child(blocks);
|
|
965
|
+
while (blk) {
|
|
966
|
+
cmark_node *next = cmark_node_next(blk);
|
|
967
|
+
cmark_node_append_child(item, blk);
|
|
968
|
+
blk = next;
|
|
969
|
+
}
|
|
970
|
+
cmark_node_free(blocks);
|
|
971
|
+
}
|
|
972
|
+
if (!json_match_char(&ccur, ']')) { cmark_node_free(item); cmark_node_free(list); free(tag); free(c_buf); return NULL; }
|
|
973
|
+
|
|
974
|
+
cmark_node_append_child(list, item);
|
|
975
|
+
|
|
976
|
+
json_skip_ws(&ccur);
|
|
977
|
+
if (*ccur.s == ',') {
|
|
978
|
+
ccur.s++;
|
|
979
|
+
continue;
|
|
980
|
+
} else if (*ccur.s == ']') {
|
|
981
|
+
break;
|
|
982
|
+
} else {
|
|
983
|
+
break;
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
if (!json_match_char(&ccur, ']')) { cmark_node_free(list); free(tag); free(c_buf); return NULL; }
|
|
987
|
+
if (!json_match_char(&ccur, ']')) { cmark_node_free(list); free(tag); free(c_buf); return NULL; }
|
|
988
|
+
node = list;
|
|
989
|
+
} else if (strcmp(tag, "BlockQuote") == 0) {
|
|
990
|
+
if (!json_match_char(&ccur, '[')) { free(tag); free(c_buf); return NULL; }
|
|
991
|
+
cmark_node *bq = cmark_node_new(CMARK_NODE_BLOCK_QUOTE);
|
|
992
|
+
if (!bq) { free(tag); free(c_buf); return NULL; }
|
|
993
|
+
cmark_node *blocks = parse_blocks_array(&ccur);
|
|
994
|
+
if (blocks) {
|
|
995
|
+
cmark_node *blk = cmark_node_first_child(blocks);
|
|
996
|
+
while (blk) {
|
|
997
|
+
cmark_node *next = cmark_node_next(blk);
|
|
998
|
+
cmark_node_append_child(bq, blk);
|
|
999
|
+
blk = next;
|
|
1000
|
+
}
|
|
1001
|
+
cmark_node_free(blocks);
|
|
1002
|
+
}
|
|
1003
|
+
if (!json_match_char(&ccur, ']')) { cmark_node_free(bq); free(tag); free(c_buf); return NULL; }
|
|
1004
|
+
node = bq;
|
|
1005
|
+
} else if (strcmp(tag, "Div") == 0) {
|
|
1006
|
+
/* c = [Attr, blocks] – skip Attr, parse inner blocks and return chain so caller appends all */
|
|
1007
|
+
if (!json_match_char(&ccur, '[')) { free(tag); free(c_buf); return NULL; }
|
|
1008
|
+
if (!json_skip_value(&ccur)) { free(tag); free(c_buf); return NULL; }
|
|
1009
|
+
if (!json_match_char(&ccur, ',')) { free(tag); free(c_buf); return NULL; }
|
|
1010
|
+
node = parse_blocks_array(&ccur);
|
|
1011
|
+
if (!json_match_char(&ccur, ']')) {
|
|
1012
|
+
if (node) cmark_node_free(node);
|
|
1013
|
+
free(tag);
|
|
1014
|
+
free(c_buf);
|
|
1015
|
+
return NULL;
|
|
1016
|
+
}
|
|
1017
|
+
} else {
|
|
1018
|
+
node = cmark_node_new(CMARK_NODE_PARAGRAPH);
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
free(tag);
|
|
1022
|
+
free(c_buf);
|
|
1023
|
+
return node;
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
static cmark_node *parse_blocks_array(json_cursor *cur) {
|
|
1027
|
+
json_skip_ws(cur);
|
|
1028
|
+
if (!json_match_char(cur, '[')) return NULL;
|
|
1029
|
+
cmark_node *dummy = cmark_node_new(CMARK_NODE_DOCUMENT);
|
|
1030
|
+
if (!dummy) return NULL;
|
|
1031
|
+
|
|
1032
|
+
json_skip_ws(cur);
|
|
1033
|
+
if (*cur->s == ']') {
|
|
1034
|
+
cur->s++;
|
|
1035
|
+
cmark_node_free(dummy);
|
|
1036
|
+
return NULL;
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
int ok = 1;
|
|
1040
|
+
while (ok) {
|
|
1041
|
+
json_skip_ws(cur);
|
|
1042
|
+
cmark_node *blk = parse_block_object(cur);
|
|
1043
|
+
if (!blk) { ok = 0; break; }
|
|
1044
|
+
while (blk) {
|
|
1045
|
+
cmark_node *next = cmark_node_next(blk);
|
|
1046
|
+
cmark_node_append_child(dummy, blk);
|
|
1047
|
+
blk = next;
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
json_skip_ws(cur);
|
|
1051
|
+
if (*cur->s == ',') {
|
|
1052
|
+
cur->s++;
|
|
1053
|
+
continue;
|
|
1054
|
+
} else if (*cur->s == ']') {
|
|
1055
|
+
break;
|
|
1056
|
+
} else {
|
|
1057
|
+
break;
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
if (!json_match_char(cur, ']')) {
|
|
1062
|
+
cmark_node_free(dummy);
|
|
1063
|
+
return NULL;
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
/* Return the dummy so the caller can iterate over its children and append each to doc;
|
|
1067
|
+
* returning the first child would require unlinking, which clears next and drops the chain. */
|
|
1068
|
+
return dummy;
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
/* ------------------------------------------------------------------------- */
|
|
1072
|
+
/* Public: Pandoc JSON -> cmark */
|
|
1073
|
+
/* ------------------------------------------------------------------------- */
|
|
1074
|
+
|
|
1075
|
+
cmark_node *apex_pandoc_json_to_cmark(const char *json,
|
|
1076
|
+
const apex_options *options) {
|
|
1077
|
+
(void)options;
|
|
1078
|
+
if (!json) return NULL;
|
|
1079
|
+
|
|
1080
|
+
json_cursor cur = { json };
|
|
1081
|
+
json_skip_ws(&cur);
|
|
1082
|
+
if (!json_match_char(&cur, '{')) return NULL;
|
|
1083
|
+
|
|
1084
|
+
cmark_node *doc = cmark_node_new(CMARK_NODE_DOCUMENT);
|
|
1085
|
+
if (!doc) return NULL;
|
|
1086
|
+
|
|
1087
|
+
/* Parse object fields; we only care about "blocks" */
|
|
1088
|
+
while (1) {
|
|
1089
|
+
json_skip_ws(&cur);
|
|
1090
|
+
if (*cur.s == '}') {
|
|
1091
|
+
cur.s++;
|
|
1092
|
+
break;
|
|
1093
|
+
}
|
|
1094
|
+
char *key = json_parse_string(&cur);
|
|
1095
|
+
if (!key) { cmark_node_free(doc); return NULL; }
|
|
1096
|
+
if (!json_match_char(&cur, ':')) { free(key); cmark_node_free(doc); return NULL; }
|
|
1097
|
+
|
|
1098
|
+
if (strcmp(key, "blocks") == 0) {
|
|
1099
|
+
cmark_node *blocks = parse_blocks_array(&cur);
|
|
1100
|
+
if (blocks) {
|
|
1101
|
+
cmark_node *blk = cmark_node_first_child(blocks);
|
|
1102
|
+
while (blk) {
|
|
1103
|
+
cmark_node *next = cmark_node_next(blk);
|
|
1104
|
+
cmark_node_append_child(doc, blk);
|
|
1105
|
+
blk = next;
|
|
1106
|
+
}
|
|
1107
|
+
cmark_node_free(blocks);
|
|
1108
|
+
}
|
|
1109
|
+
} else {
|
|
1110
|
+
/* Skip other fields (pandoc-api-version, meta, etc.) */
|
|
1111
|
+
if (!json_skip_value(&cur)) { free(key); cmark_node_free(doc); return NULL; }
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
free(key);
|
|
1115
|
+
|
|
1116
|
+
json_skip_ws(&cur);
|
|
1117
|
+
if (*cur.s == ',') {
|
|
1118
|
+
cur.s++;
|
|
1119
|
+
continue;
|
|
1120
|
+
} else if (*cur.s == '}') {
|
|
1121
|
+
cur.s++;
|
|
1122
|
+
break;
|
|
1123
|
+
} else {
|
|
1124
|
+
break;
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
return doc;
|
|
1129
|
+
}
|
|
1130
|
+
|