apex-ruby 1.0.18 → 1.0.20
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/README.md +17 -3
- data/ext/apex_ext/apex_ext.c +260 -91
- data/ext/apex_ext/apex_src/CHANGELOG.md +440 -0
- data/ext/apex_ext/apex_src/CMakeLists.txt +15 -1
- data/ext/apex_ext/apex_src/Formula/apex.rb +7 -2
- data/ext/apex_ext/apex_src/Package.swift +15 -4
- data/ext/apex_ext/apex_src/README.md +94 -35
- data/ext/apex_ext/apex_src/VERSION +1 -1
- data/ext/apex_ext/apex_src/apex-debug.log +4 -0
- data/ext/apex_ext/apex_src/apex.code-workspace +38 -0
- data/ext/apex_ext/apex_src/apex.taskpaper +12 -0
- data/ext/apex_ext/apex_src/apex_deflist_debug.log +25 -0
- data/ext/apex_ext/apex_src/buildnotes.md +1435 -0
- data/ext/apex_ext/apex_src/cli/main.c +262 -33
- data/ext/apex_ext/apex_src/debug.log +7 -0
- data/ext/apex_ext/apex_src/docs/CITATIONS_EXAMPLES.md +274 -0
- data/ext/apex_ext/apex_src/docs/CITATIONS_IMPLEMENTATION_PLAN.md +749 -0
- data/ext/apex_ext/apex_src/docs/CITATIONS_PLAN.md +744 -0
- data/ext/apex_ext/apex_src/docs/INDEX_IMPLEMENTATION_ANALYSIS.md +382 -0
- data/ext/apex_ext/apex_src/docs/plans/2025-02-27-man-page-output-implementation.md +351 -0
- data/ext/apex_ext/apex_src/docs/superpowers/plans/2026-07-12-toc-output-format.md +745 -0
- data/ext/apex_ext/apex_src/docs/superpowers/plans/2026-07-18-bear-image-attributes.md +829 -0
- data/ext/apex_ext/apex_src/docs/superpowers/plans/2026-07-19-apex-node-npm.md +1148 -0
- data/ext/apex_ext/apex_src/docs/superpowers/specs/2026-07-12-toc-output-format-design.md +161 -0
- data/ext/apex_ext/apex_src/docs/superpowers/specs/2026-07-18-bear-image-attributes-design.md +184 -0
- data/ext/apex_ext/apex_src/docs/superpowers/specs/2026-07-19-apex-node-npm-design.md +211 -0
- data/ext/apex_ext/apex_src/documentation/app-settings-table.md +67 -0
- data/ext/apex_ext/apex_src/documentation/app-transformed/Citations.md +340 -0
- data/ext/apex_ext/apex_src/documentation/app-transformed/Credits.md +11 -0
- data/ext/apex_ext/apex_src/documentation/app-transformed/Header-IDs.md +234 -0
- data/ext/apex_ext/apex_src/documentation/app-transformed/Inline-Attribute-Lists.md +516 -0
- data/ext/apex_ext/apex_src/documentation/app-transformed/Metadata-Transforms.md +461 -0
- data/ext/apex_ext/apex_src/documentation/app-transformed/Modes.md +193 -0
- data/ext/apex_ext/apex_src/documentation/app-transformed/Multi-File-Documents.md +201 -0
- data/ext/apex_ext/apex_src/documentation/app-transformed/Plugins.md +678 -0
- data/ext/apex_ext/apex_src/documentation/app-transformed/Syntax.md +1367 -0
- data/ext/apex_ext/apex_src/documentation/docsets/Apex Command Line Options.cheatsheet.txt +6 -0
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/C-API.html +774 -300
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Callouts.html +1194 -0
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Citations.html +416 -216
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Command-Line-Options.html +3544 -1094
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Configuration.html +371 -97
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Credits.html +153 -23
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Examples.html +358 -124
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Filters.html +1656 -0
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Generating-Man-Pages.html +1226 -0
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Getting-Started.html +201 -56
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Header-IDs.html +279 -109
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Home.html +200 -53
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Indices.html +2327 -0
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Inline-Attribute-Lists.html +690 -271
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Installation.html +201 -55
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Limitations-and-Roadmap.html +238 -68
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Metadata-Transforms.html +421 -221
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Modes.html +340 -78
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Multi-File-Documents.html +251 -86
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Multi-Format-Images.html +1362 -0
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Pandoc-Integration.html +277 -90
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Plugins.html +673 -346
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Quarto-Mode.html +1405 -0
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Rendering-Markdown-In-Terminal.html +1902 -0
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Syntax.html +1939 -1698
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Tables.html +1781 -0
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Troubleshooting.html +315 -140
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Usage.html +307 -113
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Using-Apex-with-Jekyll.html +1132 -0
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Writing-Tests.html +1754 -0
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Xcode-Integration.html +872 -376
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/docSet.dsidx +0 -0
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/optimizedIndex.dsidx +0 -0
- data/ext/apex_ext/apex_src/documentation/html/apex-app-docs.html +8746 -0
- data/ext/apex_ext/apex_src/documentation/shared_styles.css +20 -15
- data/ext/apex_ext/apex_src/documentation/transform_for_app.md +55 -0
- data/ext/apex_ext/apex_src/escaping-repro.md +103 -0
- data/ext/apex_ext/apex_src/examples/quick-actions/Markdown to RTF.workflow/Contents/Info.plist +8 -0
- data/ext/apex_ext/apex_src/examples/quick-actions/Markdown to RTF.workflow/Contents/QuickLook/Preview.png +0 -0
- data/ext/apex_ext/apex_src/examples/quick-actions/Markdown to RTF.workflow/Contents/document.wflow +547 -0
- data/ext/apex_ext/apex_src/examples/quick-actions/Markdown-to-RTF.workflow.zip +0 -0
- data/ext/apex_ext/apex_src/examples/quick-actions/README.md +33 -0
- data/ext/apex_ext/apex_src/include/apex/apex.h +92 -6
- data/ext/apex_ext/apex_src/include/apex/ast_rtf.h +23 -0
- data/ext/apex_ext/apex_src/include/apex/ast_terminal.h +6 -0
- data/ext/apex_ext/apex_src/include/apex/module.modulemap +5 -0
- data/ext/apex_ext/apex_src/include/apex/plugins.h +105 -0
- data/ext/apex_ext/apex_src/include/apexc.h +1 -0
- data/ext/apex_ext/apex_src/man/apex.1 +82 -78
- data/ext/apex_ext/apex_src/man/apex.1.md +81 -4
- data/ext/apex_ext/apex_src/objc/Apex.swift +73 -0
- data/ext/apex_ext/apex_src/objc/ApexPlugins.swift +79 -0
- data/ext/apex_ext/apex_src/objc/NSString+Apex.h +83 -5
- data/ext/apex_ext/apex_src/objc/NSString+Apex.m +244 -1
- data/ext/apex_ext/apex_src/objc/NSString+ApexPlugins.h +76 -0
- data/ext/apex_ext/apex_src/objc/NSString+ApexPlugins.m +169 -0
- data/ext/apex_ext/apex_src/output.html +87 -0
- data/ext/apex_ext/apex_src/plans/quarto-mode.md +442 -0
- data/ext/apex_ext/apex_src/src/_README.md +85 -32
- data/ext/apex_ext/apex_src/src/apex.c +770 -200
- data/ext/apex_ext/apex_src/src/ast_rtf.c +848 -0
- data/ext/apex_ext/apex_src/src/ast_terminal.c +184 -7
- data/ext/apex_ext/apex_src/src/extensions/abbreviations.c +10 -3
- data/ext/apex_ext/apex_src/src/extensions/advanced_tables.c +7 -11
- data/ext/apex_ext/apex_src/src/extensions/bear_image_attrs.c +467 -0
- data/ext/apex_ext/apex_src/src/extensions/bear_image_attrs.h +27 -0
- data/ext/apex_ext/apex_src/src/extensions/callouts.c +400 -4
- data/ext/apex_ext/apex_src/src/extensions/callouts.h +15 -5
- data/ext/apex_ext/apex_src/src/extensions/citations.c +26 -5
- data/ext/apex_ext/apex_src/src/extensions/code_fence_attrs.c +632 -0
- data/ext/apex_ext/apex_src/src/extensions/code_fence_attrs.h +22 -0
- data/ext/apex_ext/apex_src/src/extensions/concordance.c +632 -0
- data/ext/apex_ext/apex_src/src/extensions/definition_list.c +129 -15
- data/ext/apex_ext/apex_src/src/extensions/grid_tables.c +1050 -121
- data/ext/apex_ext/apex_src/src/extensions/html_markdown.c +569 -16
- data/ext/apex_ext/apex_src/src/extensions/ial.c +502 -52
- data/ext/apex_ext/apex_src/src/extensions/ial.h +2 -1
- data/ext/apex_ext/apex_src/src/extensions/includes.c +532 -33
- data/ext/apex_ext/apex_src/src/extensions/includes.h +7 -1
- data/ext/apex_ext/apex_src/src/extensions/index.c +1427 -140
- data/ext/apex_ext/apex_src/src/extensions/index.h +28 -0
- data/ext/apex_ext/apex_src/src/extensions/metadata.c +200 -161
- data/ext/apex_ext/apex_src/src/extensions/quarto_diagrams.c +251 -0
- data/ext/apex_ext/apex_src/src/extensions/quarto_diagrams.h +22 -0
- data/ext/apex_ext/apex_src/src/extensions/quarto_lists.c +624 -0
- data/ext/apex_ext/apex_src/src/extensions/quarto_lists.h +35 -0
- data/ext/apex_ext/apex_src/src/extensions/quarto_polish.c +264 -0
- data/ext/apex_ext/apex_src/src/extensions/quarto_polish.h +23 -0
- data/ext/apex_ext/apex_src/src/extensions/quarto_shortcodes.c +307 -0
- data/ext/apex_ext/apex_src/src/extensions/quarto_shortcodes.h +21 -0
- data/ext/apex_ext/apex_src/src/extensions/raw_content.c +351 -0
- data/ext/apex_ext/apex_src/src/extensions/raw_content.h +20 -0
- data/ext/apex_ext/apex_src/src/extensions/relaxed_tables.c +117 -3
- data/ext/apex_ext/apex_src/src/extensions/special_markers.c +53 -0
- data/ext/apex_ext/apex_src/src/extensions/syntax_highlight.c +23 -1
- data/ext/apex_ext/apex_src/src/extensions/table_html_postprocess.c +83 -92
- data/ext/apex_ext/apex_src/src/extensions/toc.c +446 -25
- data/ext/apex_ext/apex_src/src/extensions/toc.h +26 -1
- data/ext/apex_ext/apex_src/src/extensions/wiki_links.c +28 -0
- data/ext/apex_ext/apex_src/src/html_renderer.c +387 -17
- data/ext/apex_ext/apex_src/src/plugin_catalog.c +517 -0
- data/ext/apex_ext/apex_src/src/plugins_remote.c +88 -14
- data/ext/apex_ext/apex_src/src/plugins_remote.h +34 -0
- data/ext/apex_ext/apex_src/src/pretty_html.c +6 -1
- data/ext/apex_ext/apex_src/tests/fixtures/comprehensive_test.md +21 -0
- data/ext/apex_ext/apex_src/tests/fixtures/images/bear_image_attributes.md +10 -0
- data/ext/apex_ext/apex_src/tests/fixtures/includes/sections.md +19 -0
- data/ext/apex_ext/apex_src/tests/fixtures/includes/sections.txt +11 -0
- data/ext/apex_ext/apex_src/tests/fixtures/minimal_pb.md +2 -0
- data/ext/apex_ext/apex_src/tests/fixtures/obsidian-callouts.md +46 -0
- data/ext/apex_ext/apex_src/tests/fixtures/pagebreak_html_repro.md +5 -0
- data/ext/apex_ext/apex_src/tests/fixtures/python-callouts.md +47 -0
- data/ext/apex_ext/apex_src/tests/fixtures/quarto/callouts.md +3 -0
- data/ext/apex_ext/apex_src/tests/fixtures/quarto/code-attrs.md +3 -0
- data/ext/apex_ext/apex_src/tests/fixtures/quarto/diagrams.md +8 -0
- data/ext/apex_ext/apex_src/tests/fixtures/quarto/lists.md +19 -0
- data/ext/apex_ext/apex_src/tests/fixtures/quarto/raw-content.md +5 -0
- data/ext/apex_ext/apex_src/tests/fixtures/quarto/shortcodes.md +7 -0
- data/ext/apex_ext/apex_src/tests/fixtures/quarto/smoke.md +81 -0
- data/ext/apex_ext/apex_src/tests/fixtures/quarto/spans.md +5 -0
- data/ext/apex_ext/apex_src/tests/fixtures/quarto-callouts.md +47 -0
- data/ext/apex_ext/apex_src/tests/fixtures/speed.md +192 -0
- data/ext/apex_ext/apex_src/tests/fixtures/tables/grid-tables.md +37 -0
- data/ext/apex_ext/apex_src/tests/paginate_cli_test.sh +16 -0
- data/ext/apex_ext/apex_src/tests/test_bear_image_attrs.c +301 -0
- data/ext/apex_ext/apex_src/tests/test_concordance.tsv +4 -0
- data/ext/apex_ext/apex_src/tests/test_escaping_repro.c +82 -0
- data/ext/apex_ext/apex_src/tests/test_extensions.c +821 -1
- data/ext/apex_ext/apex_src/tests/test_ial.c +7 -0
- data/ext/apex_ext/apex_src/tests/test_marked_integration.c +25 -0
- data/ext/apex_ext/apex_src/tests/test_metadata.c +129 -0
- data/ext/apex_ext/apex_src/tests/test_output.c +568 -2
- data/ext/apex_ext/apex_src/tests/test_runner.c +11 -0
- data/ext/apex_ext/apex_src/tests/test_tables.c +420 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/CMakeLists.txt +1 -1
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/extensions/autolink.c +77 -1
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/extensions/table.c +135 -5
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/blocks.c +56 -1
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/config.h +38 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/main.c +1 -1
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/test/__pycache__/cmark.cpython-314.pyc +0 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/test/__pycache__/normalize.cpython-314.pyc +0 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/test/extensions.txt +69 -0
- data/ext/apex_ext/apex_src/xcode_build/CMakeScripts/ReRunCMake.make +74 -0
- data/ext/apex_ext/apex_src/xcode_build/CTestTestfile.cmake +22 -0
- data/ext/apex_ext/apex_src/xcode_build/Debug/Apex.framework/Versions/A/Apex +0 -0
- data/ext/apex_ext/apex_src/xcode_build/Debug/Apex.framework/Versions/A/Resources/Info.plist +44 -0
- data/ext/apex_ext/apex_src/xcode_build/Debug/Apex.framework/Versions/A/_CodeSignature/CodeResources +128 -0
- data/ext/apex_ext/apex_src/xcode_build/apex.xcodeproj/project.pbxproj +5118 -0
- data/ext/apex_ext/apex_src/xcode_build/apex.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +8 -0
- data/ext/apex_ext/apex_src/xcode_build/apex.xcodeproj/project.xcworkspace/xcuserdata/ttscoff.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- data/ext/apex_ext/apex_src/xcode_build/apex.xcodeproj/project.xcworkspace/xcuserdata/ttscoff.xcuserdatad/WorkspaceSettings.xcsettings +8 -0
- data/ext/apex_ext/apex_src/xcode_build/apex.xcodeproj/xcuserdata/ttscoff.xcuserdatad/xcschemes/xcschememanagement.plist +99 -0
- data/ext/apex_ext/apex_src/xcode_build/cmake_install.cmake +452 -0
- data/ext/apex_ext/apex_src/xcode_build/vendor/cmark-gfm/CMakeScripts/ReRunCMake.make +52 -0
- data/ext/apex_ext/apex_src/xcode_build/vendor/cmark-gfm/cmake_install.cmake +55 -0
- data/ext/apex_ext/apex_src/xcode_build/vendor/cmark-gfm/cmark-gfm.xcodeproj/project.pbxproj +2455 -0
- data/ext/apex_ext/apex_src/xcode_build/vendor/cmark-gfm/cmark-gfm.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +8 -0
- data/ext/apex_ext/apex_src/xcode_build/vendor/cmark-gfm/extensions/cmake_install.cmake +171 -0
- data/ext/apex_ext/apex_src/xcode_build/vendor/cmark-gfm/man/cmake_install.cmake +55 -0
- data/ext/apex_ext/apex_src/xcode_build/vendor/cmark-gfm/src/cmake_install.cmake +220 -0
- data/ext/apex_ext/apex_src/xcode_build/vendor/cmark-gfm/src/cmark-gfm_export.h +43 -0
- data/ext/apex_ext/apex_src/xcode_build/vendor/cmark-gfm/src/cmark-gfm_version.h +7 -0
- data/ext/apex_ext/apex_src/xcode_build/vendor/cmark-gfm/src/config.h +76 -0
- data/ext/apex_ext/apex_src/xcode_build/vendor/cmark-gfm/src/libcmark-gfm.pc +10 -0
- data/ext/apex_ext/extconf.rb +35 -0
- data/lib/apex/version.rb +2 -2
- metadata +117 -2
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/tempOptimizedIndex.dsidx +0 -0
|
@@ -5,10 +5,210 @@
|
|
|
5
5
|
|
|
6
6
|
#include "toc.h"
|
|
7
7
|
#include "header_ids.h"
|
|
8
|
+
#include "apex/apex.h"
|
|
8
9
|
#include <string.h>
|
|
9
10
|
#include <stdlib.h>
|
|
10
11
|
#include <stdio.h>
|
|
11
12
|
#include <ctype.h>
|
|
13
|
+
#include <stdbool.h>
|
|
14
|
+
|
|
15
|
+
/* Placeholders for backslash-escaped {{TOC...}} markers preserved through parsing. */
|
|
16
|
+
static char **escaped_toc_originals = NULL;
|
|
17
|
+
static size_t escaped_toc_count = 0;
|
|
18
|
+
|
|
19
|
+
static void clear_escaped_toc_store(void) {
|
|
20
|
+
if (escaped_toc_originals) {
|
|
21
|
+
for (size_t i = 0; i < escaped_toc_count; i++) {
|
|
22
|
+
free(escaped_toc_originals[i]);
|
|
23
|
+
}
|
|
24
|
+
free(escaped_toc_originals);
|
|
25
|
+
}
|
|
26
|
+
escaped_toc_originals = NULL;
|
|
27
|
+
escaped_toc_count = 0;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
static bool is_escaped_mmd_toc_start(const char *p) {
|
|
31
|
+
return p[0] == '\\' && p[1] == '{' && p[2] == '\\' && p[3] == '{' &&
|
|
32
|
+
strncmp(p + 4, "TOC", 3) == 0;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
static const char *find_escaped_mmd_toc_end(const char *start) {
|
|
36
|
+
const char *p = start + 7;
|
|
37
|
+
while (*p) {
|
|
38
|
+
if (p[0] == '\\' && p[1] == '}' && p[2] == '\\' && p[3] == '}') {
|
|
39
|
+
return p + 3;
|
|
40
|
+
}
|
|
41
|
+
p++;
|
|
42
|
+
}
|
|
43
|
+
return NULL;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
static char *build_unescaped_toc_marker(const char *escaped_start, const char *escaped_end) {
|
|
47
|
+
/* \{\{ ... \}\} -> {{ ... }} */
|
|
48
|
+
const char *inner = escaped_start + 4;
|
|
49
|
+
size_t inner_len = (size_t)(escaped_end - inner - 3);
|
|
50
|
+
size_t out_len = inner_len + 4;
|
|
51
|
+
char *out = malloc(out_len + 1);
|
|
52
|
+
if (!out) return NULL;
|
|
53
|
+
out[0] = '{';
|
|
54
|
+
out[1] = '{';
|
|
55
|
+
memcpy(out + 2, inner, inner_len);
|
|
56
|
+
out[2 + inner_len] = '}';
|
|
57
|
+
out[3 + inner_len] = '}';
|
|
58
|
+
out[out_len] = '\0';
|
|
59
|
+
return out;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
char *apex_protect_escaped_toc_markers(const char *text) {
|
|
63
|
+
if (!text) return NULL;
|
|
64
|
+
|
|
65
|
+
clear_escaped_toc_store();
|
|
66
|
+
|
|
67
|
+
size_t len = strlen(text);
|
|
68
|
+
size_t capacity = len + 64;
|
|
69
|
+
char *output = malloc(capacity);
|
|
70
|
+
if (!output) return NULL;
|
|
71
|
+
|
|
72
|
+
const char *read = text;
|
|
73
|
+
char *write = output;
|
|
74
|
+
size_t remaining = capacity;
|
|
75
|
+
|
|
76
|
+
while (*read) {
|
|
77
|
+
if (is_escaped_mmd_toc_start(read)) {
|
|
78
|
+
const char *end = find_escaped_mmd_toc_end(read);
|
|
79
|
+
if (end) {
|
|
80
|
+
char *original = build_unescaped_toc_marker(read, end);
|
|
81
|
+
if (!original) {
|
|
82
|
+
free(output);
|
|
83
|
+
clear_escaped_toc_store();
|
|
84
|
+
return NULL;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
char **new_store = realloc(escaped_toc_originals,
|
|
88
|
+
(escaped_toc_count + 1) * sizeof(char *));
|
|
89
|
+
if (!new_store) {
|
|
90
|
+
free(original);
|
|
91
|
+
free(output);
|
|
92
|
+
clear_escaped_toc_store();
|
|
93
|
+
return NULL;
|
|
94
|
+
}
|
|
95
|
+
escaped_toc_originals = new_store;
|
|
96
|
+
escaped_toc_originals[escaped_toc_count++] = original;
|
|
97
|
+
|
|
98
|
+
char placeholder[64];
|
|
99
|
+
int ph_len = snprintf(placeholder, sizeof(placeholder),
|
|
100
|
+
"@APEX_ESCAPED_TOC_%zu@", escaped_toc_count - 1);
|
|
101
|
+
if ((size_t)ph_len >= remaining) {
|
|
102
|
+
size_t written = (size_t)(write - output);
|
|
103
|
+
capacity = written + (size_t)ph_len + len + 64;
|
|
104
|
+
char *new_output = realloc(output, capacity);
|
|
105
|
+
if (!new_output) {
|
|
106
|
+
free(output);
|
|
107
|
+
clear_escaped_toc_store();
|
|
108
|
+
return NULL;
|
|
109
|
+
}
|
|
110
|
+
output = new_output;
|
|
111
|
+
write = output + written;
|
|
112
|
+
remaining = capacity - written;
|
|
113
|
+
}
|
|
114
|
+
memcpy(write, placeholder, (size_t)ph_len);
|
|
115
|
+
write += ph_len;
|
|
116
|
+
remaining -= (size_t)ph_len;
|
|
117
|
+
read = end + 1;
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (remaining == 0) {
|
|
123
|
+
size_t written = (size_t)(write - output);
|
|
124
|
+
capacity = (written + len + 64) * 2;
|
|
125
|
+
char *new_output = realloc(output, capacity);
|
|
126
|
+
if (!new_output) {
|
|
127
|
+
free(output);
|
|
128
|
+
clear_escaped_toc_store();
|
|
129
|
+
return NULL;
|
|
130
|
+
}
|
|
131
|
+
output = new_output;
|
|
132
|
+
write = output + written;
|
|
133
|
+
remaining = capacity - written;
|
|
134
|
+
}
|
|
135
|
+
*write++ = *read++;
|
|
136
|
+
remaining--;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
*write = '\0';
|
|
140
|
+
return output;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
char *apex_restore_escaped_toc_markers(const char *html) {
|
|
144
|
+
if (!html) return NULL;
|
|
145
|
+
if (escaped_toc_count == 0) return strdup(html);
|
|
146
|
+
|
|
147
|
+
size_t len = strlen(html);
|
|
148
|
+
size_t capacity = len + escaped_toc_count * 32;
|
|
149
|
+
char *output = malloc(capacity);
|
|
150
|
+
if (!output) return NULL;
|
|
151
|
+
|
|
152
|
+
const char *read = html;
|
|
153
|
+
char *write = output;
|
|
154
|
+
size_t remaining = capacity;
|
|
155
|
+
|
|
156
|
+
while (*read) {
|
|
157
|
+
if (strncmp(read, "@APEX_ESCAPED_TOC_", 18) == 0) {
|
|
158
|
+
const char *idx_start = read + 18;
|
|
159
|
+
const char *idx_end = strchr(idx_start, '@');
|
|
160
|
+
if (idx_end && idx_end > idx_start) {
|
|
161
|
+
char idx_buf[32];
|
|
162
|
+
size_t idx_len = (size_t)(idx_end - idx_start);
|
|
163
|
+
if (idx_len < sizeof(idx_buf)) {
|
|
164
|
+
memcpy(idx_buf, idx_start, idx_len);
|
|
165
|
+
idx_buf[idx_len] = '\0';
|
|
166
|
+
size_t index = (size_t)atoi(idx_buf);
|
|
167
|
+
if (index < escaped_toc_count && escaped_toc_originals[index]) {
|
|
168
|
+
const char *original = escaped_toc_originals[index];
|
|
169
|
+
size_t orig_len = strlen(original);
|
|
170
|
+
if (orig_len >= remaining) {
|
|
171
|
+
size_t written = (size_t)(write - output);
|
|
172
|
+
capacity = written + orig_len + 64;
|
|
173
|
+
char *new_output = realloc(output, capacity);
|
|
174
|
+
if (!new_output) {
|
|
175
|
+
free(output);
|
|
176
|
+
return NULL;
|
|
177
|
+
}
|
|
178
|
+
output = new_output;
|
|
179
|
+
write = output + written;
|
|
180
|
+
remaining = capacity - written;
|
|
181
|
+
}
|
|
182
|
+
memcpy(write, original, orig_len);
|
|
183
|
+
write += orig_len;
|
|
184
|
+
remaining -= orig_len;
|
|
185
|
+
read = idx_end + 1;
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (remaining == 0) {
|
|
193
|
+
size_t written = (size_t)(write - output);
|
|
194
|
+
capacity = (written + len + 64) * 2;
|
|
195
|
+
char *new_output = realloc(output, capacity);
|
|
196
|
+
if (!new_output) {
|
|
197
|
+
free(output);
|
|
198
|
+
return NULL;
|
|
199
|
+
}
|
|
200
|
+
output = new_output;
|
|
201
|
+
write = output + written;
|
|
202
|
+
remaining = capacity - written;
|
|
203
|
+
}
|
|
204
|
+
*write++ = *read++;
|
|
205
|
+
remaining--;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
*write = '\0';
|
|
209
|
+
clear_escaped_toc_store();
|
|
210
|
+
return output;
|
|
211
|
+
}
|
|
12
212
|
|
|
13
213
|
/**
|
|
14
214
|
* Collect headers from AST
|
|
@@ -20,6 +220,61 @@ typedef struct header_item {
|
|
|
20
220
|
struct header_item *next;
|
|
21
221
|
} header_item;
|
|
22
222
|
|
|
223
|
+
/* Normalize heading text for TOC labels:
|
|
224
|
+
* - trim leading/trailing whitespace
|
|
225
|
+
* - collapse internal whitespace runs to a single space
|
|
226
|
+
*/
|
|
227
|
+
static char *normalize_toc_text(const char *text) {
|
|
228
|
+
if (!text) return strdup("");
|
|
229
|
+
|
|
230
|
+
size_t len = strlen(text);
|
|
231
|
+
char *out = malloc(len + 1);
|
|
232
|
+
if (!out) return strdup("");
|
|
233
|
+
|
|
234
|
+
const unsigned char *read = (const unsigned char *)text;
|
|
235
|
+
while (*read && isspace(*read)) read++;
|
|
236
|
+
|
|
237
|
+
char *write = out;
|
|
238
|
+
int pending_space = 0;
|
|
239
|
+
|
|
240
|
+
while (*read) {
|
|
241
|
+
if (isspace(*read)) {
|
|
242
|
+
pending_space = 1;
|
|
243
|
+
} else {
|
|
244
|
+
if (pending_space && write > out) {
|
|
245
|
+
*write++ = ' ';
|
|
246
|
+
}
|
|
247
|
+
*write++ = (char)*read;
|
|
248
|
+
pending_space = 0;
|
|
249
|
+
}
|
|
250
|
+
read++;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
*write = '\0';
|
|
254
|
+
return out;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
static char *heading_id_from_attrs_or_text(const char *attrs, const char *text,
|
|
258
|
+
apex_id_format_t id_format) {
|
|
259
|
+
if (attrs) {
|
|
260
|
+
const char *id_attr = strstr(attrs, "id=\"");
|
|
261
|
+
if (id_attr) {
|
|
262
|
+
const char *id_start = id_attr + 4;
|
|
263
|
+
const char *id_end = strchr(id_start, '"');
|
|
264
|
+
if (id_end && id_end > id_start) {
|
|
265
|
+
size_t id_len = (size_t)(id_end - id_start);
|
|
266
|
+
char *id = malloc(id_len + 1);
|
|
267
|
+
if (id) {
|
|
268
|
+
memcpy(id, id_start, id_len);
|
|
269
|
+
id[id_len] = '\0';
|
|
270
|
+
return id;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
return apex_generate_header_id(text, id_format);
|
|
276
|
+
}
|
|
277
|
+
|
|
23
278
|
static void free_headers(header_item *headers) {
|
|
24
279
|
while (headers) {
|
|
25
280
|
header_item *next = headers->next;
|
|
@@ -30,12 +285,42 @@ static void free_headers(header_item *headers) {
|
|
|
30
285
|
}
|
|
31
286
|
}
|
|
32
287
|
|
|
288
|
+
static void clamp_toc_levels(int *min_level, int *max_level) {
|
|
289
|
+
if (*min_level < 1) *min_level = 1;
|
|
290
|
+
if (*max_level < 1) *max_level = 1;
|
|
291
|
+
if (*min_level > 6) *min_level = 6;
|
|
292
|
+
if (*max_level > 6) *max_level = 6;
|
|
293
|
+
if (*min_level > *max_level) *min_level = *max_level;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
static bool append_toc_markdown(char **buffer, size_t *capacity, size_t *length,
|
|
297
|
+
const char *text, size_t text_len) {
|
|
298
|
+
if (!buffer || !*buffer || !capacity || !length) return false;
|
|
299
|
+
|
|
300
|
+
if (*length + text_len + 1 > *capacity) {
|
|
301
|
+
size_t new_capacity = *capacity;
|
|
302
|
+
while (*length + text_len + 1 > new_capacity) {
|
|
303
|
+
new_capacity *= 2;
|
|
304
|
+
}
|
|
305
|
+
char *new_buffer = realloc(*buffer, new_capacity);
|
|
306
|
+
if (!new_buffer) return false;
|
|
307
|
+
*buffer = new_buffer;
|
|
308
|
+
*capacity = new_capacity;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
memcpy(*buffer + *length, text, text_len);
|
|
312
|
+
*length += text_len;
|
|
313
|
+
(*buffer)[*length] = '\0';
|
|
314
|
+
return true;
|
|
315
|
+
}
|
|
316
|
+
|
|
33
317
|
|
|
34
318
|
|
|
35
319
|
/**
|
|
36
320
|
* Collect all headers from document
|
|
37
321
|
*/
|
|
38
|
-
static header_item *collect_headers(cmark_node *node, header_item **tail
|
|
322
|
+
static header_item *collect_headers(cmark_node *node, header_item **tail,
|
|
323
|
+
apex_id_format_t id_format) {
|
|
39
324
|
if (!node) return NULL;
|
|
40
325
|
|
|
41
326
|
header_item *headers = NULL;
|
|
@@ -54,8 +339,10 @@ static header_item *collect_headers(cmark_node *node, header_item **tail) {
|
|
|
54
339
|
header_item *item = malloc(sizeof(header_item));
|
|
55
340
|
if (item) {
|
|
56
341
|
item->level = cmark_node_get_heading_level(node);
|
|
57
|
-
|
|
58
|
-
item->
|
|
342
|
+
char *raw_text = apex_extract_heading_text(node);
|
|
343
|
+
item->text = normalize_toc_text(raw_text);
|
|
344
|
+
free(raw_text);
|
|
345
|
+
item->id = heading_id_from_attrs_or_text(attrs, item->text, id_format);
|
|
59
346
|
item->next = NULL;
|
|
60
347
|
|
|
61
348
|
if (*tail) {
|
|
@@ -70,7 +357,7 @@ static header_item *collect_headers(cmark_node *node, header_item **tail) {
|
|
|
70
357
|
|
|
71
358
|
/* Recursively process children */
|
|
72
359
|
for (cmark_node *child = cmark_node_first_child(node); child; child = cmark_node_next(child)) {
|
|
73
|
-
header_item *child_headers = collect_headers(child, tail);
|
|
360
|
+
header_item *child_headers = collect_headers(child, tail, id_format);
|
|
74
361
|
if (!headers) headers = child_headers;
|
|
75
362
|
}
|
|
76
363
|
|
|
@@ -91,7 +378,7 @@ static char *generate_toc_html(header_item *headers, int min_level, int max_leve
|
|
|
91
378
|
char *write = html;
|
|
92
379
|
size_t remaining = capacity;
|
|
93
380
|
int current_level = 0;
|
|
94
|
-
int last_level = 0; /* level of last item added (0 = none yet) */
|
|
381
|
+
int last_level = 0; /* normalized level of last item added (0 = none yet) */
|
|
95
382
|
|
|
96
383
|
#define APPEND(str) do { \
|
|
97
384
|
size_t len = strlen(str); \
|
|
@@ -107,9 +394,11 @@ static char *generate_toc_html(header_item *headers, int min_level, int max_leve
|
|
|
107
394
|
for (header_item *h = headers; h; h = h->next) {
|
|
108
395
|
/* Skip headers outside min/max range */
|
|
109
396
|
if (h->level < min_level || h->level > max_level) continue;
|
|
397
|
+
int target_level = h->level - min_level + 1;
|
|
398
|
+
if (target_level < 1) target_level = 1;
|
|
110
399
|
|
|
111
400
|
/* Going up: close </ul></li> for each level (nested ul inside parent li) */
|
|
112
|
-
while (current_level >
|
|
401
|
+
while (current_level > target_level) {
|
|
113
402
|
APPEND("</ul>\n</li>\n");
|
|
114
403
|
current_level--;
|
|
115
404
|
}
|
|
@@ -117,7 +406,7 @@ static char *generate_toc_html(header_item *headers, int min_level, int max_leve
|
|
|
117
406
|
/* Going down: open one <ul> inside the previous li before adding child.
|
|
118
407
|
* At root (current_level 0): only open one ul - never ul > ul.
|
|
119
408
|
* When going deeper: open exactly one ul per step - never ul > ul. */
|
|
120
|
-
while (current_level <
|
|
409
|
+
while (current_level < target_level) {
|
|
121
410
|
if (current_level == 0) {
|
|
122
411
|
APPEND("<ul>\n");
|
|
123
412
|
current_level = 1;
|
|
@@ -133,7 +422,7 @@ static char *generate_toc_html(header_item *headers, int min_level, int max_leve
|
|
|
133
422
|
}
|
|
134
423
|
|
|
135
424
|
/* Close previous li when adding sibling (same or shallower level) */
|
|
136
|
-
if (last_level > 0 &&
|
|
425
|
+
if (last_level > 0 && target_level <= last_level) {
|
|
137
426
|
APPEND("</li>\n");
|
|
138
427
|
}
|
|
139
428
|
|
|
@@ -142,7 +431,7 @@ static char *generate_toc_html(header_item *headers, int min_level, int max_leve
|
|
|
142
431
|
snprintf(item, sizeof(item), "<li><a href=\"#%s\">%s</a>",
|
|
143
432
|
h->id, h->text);
|
|
144
433
|
APPEND(item);
|
|
145
|
-
last_level =
|
|
434
|
+
last_level = target_level;
|
|
146
435
|
}
|
|
147
436
|
|
|
148
437
|
/* Close remaining: </li></ul> for each open level */
|
|
@@ -159,6 +448,108 @@ static char *generate_toc_html(header_item *headers, int min_level, int max_leve
|
|
|
159
448
|
return html;
|
|
160
449
|
}
|
|
161
450
|
|
|
451
|
+
void apex_toc_entries_free(apex_toc_entry *entries, size_t count) {
|
|
452
|
+
if (!entries) return;
|
|
453
|
+
for (size_t i = 0; i < count; i++) {
|
|
454
|
+
free(entries[i].text);
|
|
455
|
+
free(entries[i].id);
|
|
456
|
+
}
|
|
457
|
+
free(entries);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
apex_toc_entry *apex_generate_toc_entries(cmark_node *document, int id_format,
|
|
461
|
+
int min_level, int max_level,
|
|
462
|
+
size_t *out_count) {
|
|
463
|
+
if (out_count) *out_count = 0;
|
|
464
|
+
if (!document || !out_count) return NULL;
|
|
465
|
+
|
|
466
|
+
clamp_toc_levels(&min_level, &max_level);
|
|
467
|
+
|
|
468
|
+
header_item *tail = NULL;
|
|
469
|
+
header_item *headers = collect_headers(document, &tail, (apex_id_format_t)id_format);
|
|
470
|
+
if (!headers) return NULL;
|
|
471
|
+
|
|
472
|
+
size_t count = 0;
|
|
473
|
+
for (header_item *h = headers; h; h = h->next) {
|
|
474
|
+
if (h->level >= min_level && h->level <= max_level) count++;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
if (count == 0) {
|
|
478
|
+
free_headers(headers);
|
|
479
|
+
return NULL;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
apex_toc_entry *entries = calloc(count, sizeof(apex_toc_entry));
|
|
483
|
+
if (!entries) {
|
|
484
|
+
free_headers(headers);
|
|
485
|
+
return NULL;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
size_t idx = 0;
|
|
489
|
+
for (header_item *h = headers; h; h = h->next) {
|
|
490
|
+
if (h->level < min_level || h->level > max_level) continue;
|
|
491
|
+
entries[idx].level = h->level;
|
|
492
|
+
entries[idx].text = h->text ? strdup(h->text) : strdup("");
|
|
493
|
+
entries[idx].id = h->id ? strdup(h->id) : strdup("");
|
|
494
|
+
if (!entries[idx].text || !entries[idx].id) {
|
|
495
|
+
apex_toc_entries_free(entries, idx + 1);
|
|
496
|
+
free_headers(headers);
|
|
497
|
+
return NULL;
|
|
498
|
+
}
|
|
499
|
+
idx++;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
free_headers(headers);
|
|
503
|
+
*out_count = count;
|
|
504
|
+
return entries;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
char *apex_generate_toc_markdown(cmark_node *document, int id_format,
|
|
508
|
+
int min_level, int max_level) {
|
|
509
|
+
size_t count = 0;
|
|
510
|
+
apex_toc_entry *entries = apex_generate_toc_entries(document, id_format,
|
|
511
|
+
min_level, max_level,
|
|
512
|
+
&count);
|
|
513
|
+
if (!entries || count == 0) {
|
|
514
|
+
apex_toc_entries_free(entries, count);
|
|
515
|
+
return strdup("");
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
size_t capacity = 1024;
|
|
519
|
+
size_t length = 0;
|
|
520
|
+
char *markdown = malloc(capacity);
|
|
521
|
+
if (!markdown) {
|
|
522
|
+
apex_toc_entries_free(entries, count);
|
|
523
|
+
return strdup("");
|
|
524
|
+
}
|
|
525
|
+
markdown[0] = '\0';
|
|
526
|
+
|
|
527
|
+
clamp_toc_levels(&min_level, &max_level);
|
|
528
|
+
|
|
529
|
+
for (size_t i = 0; i < count; i++) {
|
|
530
|
+
int indent = (entries[i].level - min_level) * 2;
|
|
531
|
+
for (int j = 0; j < indent; j++) {
|
|
532
|
+
if (!append_toc_markdown(&markdown, &capacity, &length, " ", 1)) goto fail;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
const char *text = entries[i].text ? entries[i].text : "";
|
|
536
|
+
const char *id = entries[i].id ? entries[i].id : "";
|
|
537
|
+
if (!append_toc_markdown(&markdown, &capacity, &length, "- [", 3)) goto fail;
|
|
538
|
+
if (!append_toc_markdown(&markdown, &capacity, &length, text, strlen(text))) goto fail;
|
|
539
|
+
if (!append_toc_markdown(&markdown, &capacity, &length, "](#", 3)) goto fail;
|
|
540
|
+
if (!append_toc_markdown(&markdown, &capacity, &length, id, strlen(id))) goto fail;
|
|
541
|
+
if (!append_toc_markdown(&markdown, &capacity, &length, ")\n", 2)) goto fail;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
apex_toc_entries_free(entries, count);
|
|
545
|
+
return markdown;
|
|
546
|
+
|
|
547
|
+
fail:
|
|
548
|
+
free(markdown);
|
|
549
|
+
apex_toc_entries_free(entries, count);
|
|
550
|
+
return strdup("");
|
|
551
|
+
}
|
|
552
|
+
|
|
162
553
|
/**
|
|
163
554
|
* Return true if position 'pos' in 'html' is inside a <code> or <pre> element.
|
|
164
555
|
* Used to skip TOC markers that appear in code blocks or inline code.
|
|
@@ -218,42 +609,76 @@ static int is_inside_code_or_pre(const char *html, size_t pos) {
|
|
|
218
609
|
/**
|
|
219
610
|
* Parse TOC marker for min/max levels
|
|
220
611
|
*/
|
|
221
|
-
static void parse_toc_marker(const char *marker, int *min_level, int *max_level
|
|
222
|
-
|
|
223
|
-
|
|
612
|
+
static void parse_toc_marker(const char *marker, int *min_level, int *max_level,
|
|
613
|
+
int default_min, int default_max) {
|
|
614
|
+
int specified_min = 0;
|
|
615
|
+
int specified_max = 0;
|
|
616
|
+
|
|
617
|
+
*min_level = default_min;
|
|
618
|
+
*max_level = default_max;
|
|
619
|
+
|
|
620
|
+
if (!marker) return;
|
|
621
|
+
|
|
622
|
+
/* Only parse inside the marker, never beyond marker end into following HTML. */
|
|
623
|
+
const char *marker_end = strstr(marker, "}}");
|
|
624
|
+
if (!marker_end) marker_end = strstr(marker, "-->");
|
|
625
|
+
if (!marker_end) {
|
|
626
|
+
marker_end = marker + strlen(marker);
|
|
627
|
+
}
|
|
628
|
+
size_t marker_len = (size_t)(marker_end - marker);
|
|
629
|
+
char *marker_copy = (char *)malloc(marker_len + 1);
|
|
630
|
+
if (!marker_copy) return;
|
|
631
|
+
memcpy(marker_copy, marker, marker_len);
|
|
632
|
+
marker_copy[marker_len] = '\0';
|
|
224
633
|
|
|
225
634
|
/* Look for max and min parameters */
|
|
226
|
-
const char *max_str = strstr(
|
|
227
|
-
const char *min_str = strstr(
|
|
635
|
+
const char *max_str = strstr(marker_copy, "max");
|
|
636
|
+
const char *min_str = strstr(marker_copy, "min");
|
|
228
637
|
|
|
229
638
|
if (max_str) {
|
|
230
639
|
max_str += 3;
|
|
231
640
|
while (*max_str && !isdigit((unsigned char)*max_str)) max_str++;
|
|
232
|
-
if (*max_str)
|
|
641
|
+
if (*max_str) {
|
|
642
|
+
*max_level = atoi(max_str);
|
|
643
|
+
specified_max = 1;
|
|
644
|
+
}
|
|
233
645
|
}
|
|
234
646
|
|
|
235
647
|
if (min_str) {
|
|
236
648
|
min_str += 3;
|
|
237
649
|
while (*min_str && !isdigit((unsigned char)*min_str)) min_str++;
|
|
238
|
-
if (*min_str)
|
|
650
|
+
if (*min_str) {
|
|
651
|
+
*min_level = atoi(min_str);
|
|
652
|
+
specified_min = 1;
|
|
653
|
+
}
|
|
239
654
|
}
|
|
240
655
|
|
|
241
656
|
/* Check for Pandoc style {{TOC:2-5}} */
|
|
242
|
-
const char *colon = strchr(
|
|
657
|
+
const char *colon = strchr(marker_copy, ':');
|
|
243
658
|
if (colon) {
|
|
244
659
|
colon++;
|
|
245
660
|
while (*colon && isspace((unsigned char)*colon)) colon++;
|
|
246
661
|
if (isdigit((unsigned char)*colon)) {
|
|
247
662
|
*min_level = atoi(colon);
|
|
663
|
+
specified_min = 1;
|
|
248
664
|
const char *dash = strchr(colon, '-');
|
|
249
665
|
if (dash) {
|
|
250
666
|
dash++;
|
|
251
667
|
if (isdigit((unsigned char)*dash)) {
|
|
252
668
|
*max_level = atoi(dash);
|
|
669
|
+
specified_max = 1;
|
|
253
670
|
}
|
|
254
671
|
}
|
|
255
672
|
}
|
|
256
673
|
}
|
|
674
|
+
free(marker_copy);
|
|
675
|
+
|
|
676
|
+
if (specified_min || specified_max) {
|
|
677
|
+
if (!specified_min) *min_level = 1;
|
|
678
|
+
if (!specified_max) *max_level = 6;
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
clamp_toc_levels(min_level, max_level);
|
|
257
682
|
}
|
|
258
683
|
|
|
259
684
|
/**
|
|
@@ -299,7 +724,8 @@ static const char *find_toc_marker_not_in_code(const char *html, int *is_html_co
|
|
|
299
724
|
/**
|
|
300
725
|
* Process TOC markers in HTML
|
|
301
726
|
*/
|
|
302
|
-
char *apex_process_toc(const char *html, cmark_node *document, int id_format
|
|
727
|
+
char *apex_process_toc(const char *html, cmark_node *document, int id_format,
|
|
728
|
+
int default_min, int default_max) {
|
|
303
729
|
if (!html || !document) return html ? strdup(html) : NULL;
|
|
304
730
|
|
|
305
731
|
int is_html_comment = 0;
|
|
@@ -311,17 +737,12 @@ char *apex_process_toc(const char *html, cmark_node *document, int id_format) {
|
|
|
311
737
|
|
|
312
738
|
/* Collect headers from document */
|
|
313
739
|
header_item *tail = NULL;
|
|
314
|
-
header_item *headers = collect_headers(document, &tail);
|
|
740
|
+
header_item *headers = collect_headers(document, &tail, (apex_id_format_t)id_format);
|
|
315
741
|
if (!headers) return strdup(html);
|
|
316
742
|
|
|
317
|
-
/* Generate IDs for all headers using the specified format */
|
|
318
|
-
for (header_item *h = headers; h; h = h->next) {
|
|
319
|
-
h->id = apex_generate_header_id(h->text, (apex_id_format_t)id_format);
|
|
320
|
-
}
|
|
321
|
-
|
|
322
743
|
/* Parse the marker for min/max levels */
|
|
323
744
|
int min_level, max_level;
|
|
324
|
-
parse_toc_marker(marker, &min_level, &max_level);
|
|
745
|
+
parse_toc_marker(marker, &min_level, &max_level, default_min, default_max);
|
|
325
746
|
|
|
326
747
|
/* Generate TOC HTML */
|
|
327
748
|
char *toc_html = generate_toc_html(headers, min_level, max_level);
|
|
@@ -23,8 +23,33 @@ extern "C" {
|
|
|
23
23
|
* @param html The HTML output
|
|
24
24
|
* @param document The AST document
|
|
25
25
|
* @param id_format 0=GFM (with dashes), 1=MMD (no dashes)
|
|
26
|
+
* @param default_min Default minimum heading level for bare TOC markers
|
|
27
|
+
* @param default_max Default maximum heading level for bare TOC markers
|
|
26
28
|
*/
|
|
27
|
-
char *apex_process_toc(const char *html, cmark_node *document, int id_format
|
|
29
|
+
char *apex_process_toc(const char *html, cmark_node *document, int id_format,
|
|
30
|
+
int default_min, int default_max);
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Generate a Markdown table of contents from document headings.
|
|
34
|
+
* Returns a newly allocated string containing "- [text](#id)" entries.
|
|
35
|
+
* @param document The AST document
|
|
36
|
+
* @param id_format 0=GFM (with dashes), 1=MMD (no dashes)
|
|
37
|
+
* @param min_level Inclusive minimum heading level
|
|
38
|
+
* @param max_level Inclusive maximum heading level
|
|
39
|
+
*/
|
|
40
|
+
char *apex_generate_toc_markdown(cmark_node *document, int id_format,
|
|
41
|
+
int min_level, int max_level);
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Replace backslash-escaped {{TOC...}} markers with placeholders before parsing.
|
|
45
|
+
* Call before markdown parse when TOC processing will run later.
|
|
46
|
+
*/
|
|
47
|
+
char *apex_protect_escaped_toc_markers(const char *text);
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Restore placeholders to literal {{TOC...}} text in HTML after TOC processing.
|
|
51
|
+
*/
|
|
52
|
+
char *apex_restore_escaped_toc_markers(const char *html);
|
|
28
53
|
|
|
29
54
|
#ifdef __cplusplus
|
|
30
55
|
}
|
|
@@ -332,6 +332,11 @@ static cmark_node *match_wiki_link(cmark_syntax_extension *self,
|
|
|
332
332
|
const char *input = (const char *)chunk->data + pos;
|
|
333
333
|
int remaining = chunk->len - pos;
|
|
334
334
|
|
|
335
|
+
/* Do not parse Obsidian embeds ![[...]] as regular wiki links. */
|
|
336
|
+
if (pos > 0 && chunk->data[pos - 1] == '!') {
|
|
337
|
+
return NULL;
|
|
338
|
+
}
|
|
339
|
+
|
|
335
340
|
/* CRITICAL: Must check for [[ to distinguish from regular markdown links [text](url) */
|
|
336
341
|
if (remaining < 2 || input[0] != '[' || input[1] != '[') {
|
|
337
342
|
return NULL; /* Not a wiki link, let standard link parser handle it */
|
|
@@ -501,6 +506,29 @@ void apex_process_wiki_links_in_tree(cmark_node *node, wiki_link_config *config)
|
|
|
501
506
|
|
|
502
507
|
size_t content_len = (size_t)(close - (open + 2));
|
|
503
508
|
|
|
509
|
+
/* Preserve Obsidian embeds ![[...]] as literal text so include
|
|
510
|
+
* preprocessing can handle them, and avoid rendering !<a ...>. */
|
|
511
|
+
if (open > literal && open[-1] == '!') {
|
|
512
|
+
size_t raw_len = (size_t)((close + 2) - open);
|
|
513
|
+
char *raw = malloc(raw_len + 1);
|
|
514
|
+
if (raw) {
|
|
515
|
+
memcpy(raw, open, raw_len);
|
|
516
|
+
raw[raw_len] = '\0';
|
|
517
|
+
cmark_node *text = cmark_node_new(CMARK_NODE_TEXT);
|
|
518
|
+
cmark_node_set_literal(text, raw);
|
|
519
|
+
free(raw);
|
|
520
|
+
if (!insert_after) {
|
|
521
|
+
cmark_node_insert_before(node, text);
|
|
522
|
+
} else {
|
|
523
|
+
cmark_node_insert_after(insert_after, text);
|
|
524
|
+
}
|
|
525
|
+
insert_after = text;
|
|
526
|
+
changed = true;
|
|
527
|
+
}
|
|
528
|
+
cursor = close + 2;
|
|
529
|
+
continue;
|
|
530
|
+
}
|
|
531
|
+
|
|
504
532
|
/* If empty content, keep literal text and continue */
|
|
505
533
|
if (content_len == 0) {
|
|
506
534
|
size_t raw_len = (size_t)((close + 2) - open);
|