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
|
@@ -4,11 +4,79 @@
|
|
|
4
4
|
|
|
5
5
|
#include "test_helpers.h"
|
|
6
6
|
#include "apex/apex.h"
|
|
7
|
+
#include "../src/html_renderer.h"
|
|
7
8
|
#include "../src/extensions/advanced_footnotes.h"
|
|
8
9
|
#include <stdio.h>
|
|
9
10
|
#include <stdlib.h>
|
|
10
11
|
#include <string.h>
|
|
11
12
|
|
|
13
|
+
static char *replace_first_substring(const char *text,
|
|
14
|
+
const char *needle,
|
|
15
|
+
const char *replacement) {
|
|
16
|
+
const char *pos = strstr(text, needle);
|
|
17
|
+
if (!pos) {
|
|
18
|
+
return NULL;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
size_t prefix_len = (size_t)(pos - text);
|
|
22
|
+
size_t needle_len = strlen(needle);
|
|
23
|
+
size_t replacement_len = strlen(replacement);
|
|
24
|
+
size_t suffix_len = strlen(pos + needle_len);
|
|
25
|
+
char *result = malloc(prefix_len + replacement_len + suffix_len + 1);
|
|
26
|
+
if (!result) {
|
|
27
|
+
return NULL;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
memcpy(result, text, prefix_len);
|
|
31
|
+
memcpy(result + prefix_len, replacement, replacement_len);
|
|
32
|
+
memcpy(result + prefix_len + replacement_len, pos + needle_len, suffix_len + 1);
|
|
33
|
+
return result;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
static char *include_preparse_plugin_callback(const char *text,
|
|
37
|
+
__attribute__((unused)) const char *id_plugin,
|
|
38
|
+
apex_plugin_phase_mask phase,
|
|
39
|
+
__attribute__((unused)) const apex_options *options) {
|
|
40
|
+
if (!(phase & APEX_PLUGIN_PHASE_PRE_PARSE) || !text) {
|
|
41
|
+
return NULL;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
char *current = strdup(text);
|
|
45
|
+
if (!current) {
|
|
46
|
+
return NULL;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
bool changed = false;
|
|
50
|
+
char *next = replace_first_substring(current, "Included Content", "Plugin Included Content");
|
|
51
|
+
if (next) {
|
|
52
|
+
free(current);
|
|
53
|
+
current = next;
|
|
54
|
+
changed = true;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
next = replace_first_substring(current, "## Section 2", "## Plugin Section 2");
|
|
58
|
+
if (next) {
|
|
59
|
+
free(current);
|
|
60
|
+
current = next;
|
|
61
|
+
changed = true;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (!changed) {
|
|
65
|
+
free(current);
|
|
66
|
+
return NULL;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return current;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
static void include_preparse_plugin_register(apex_plugin_manager *manager,
|
|
73
|
+
__attribute__((unused)) const apex_options *options) {
|
|
74
|
+
apex_plugin_register(manager,
|
|
75
|
+
"include-preparse-plugin",
|
|
76
|
+
APEX_PLUGIN_PHASE_PRE_PARSE,
|
|
77
|
+
include_preparse_plugin_callback);
|
|
78
|
+
}
|
|
79
|
+
|
|
12
80
|
void test_math(void) {
|
|
13
81
|
int suite_failures = suite_start();
|
|
14
82
|
print_suite_title("Math Support Tests", false, true);
|
|
@@ -172,10 +240,331 @@ void test_processor_modes(void) {
|
|
|
172
240
|
assert_contains(html, "<h1", "Unified mode works");
|
|
173
241
|
apex_free_string(html);
|
|
174
242
|
|
|
243
|
+
/* Test Quarto mode */
|
|
244
|
+
apex_options quarto_opts = apex_options_for_mode(APEX_MODE_QUARTO);
|
|
245
|
+
html = apex_markdown_to_html(markdown, strlen(markdown), &quarto_opts);
|
|
246
|
+
assert_contains(html, "<h1", "Quarto mode works");
|
|
247
|
+
test_result(quarto_opts.enable_quarto_callouts == true, "Quarto mode enables quarto callouts by default");
|
|
248
|
+
test_result(quarto_opts.enable_quarto_extensions == true, "Quarto mode enables quarto extensions by default");
|
|
249
|
+
test_result(quarto_opts.enable_quarto_diagrams == true, "Quarto mode enables quarto diagrams by default");
|
|
250
|
+
test_result(quarto_opts.enable_quarto_shortcodes == true, "Quarto mode enables quarto shortcodes by default");
|
|
251
|
+
test_result(quarto_opts.enable_quarto_xrefs == true, "Quarto mode enables quarto xrefs by default");
|
|
252
|
+
test_result(quarto_opts.enable_quarto_strict_lists == false, "Quarto mode keeps strict lists off by default");
|
|
253
|
+
apex_free_string(html);
|
|
254
|
+
|
|
175
255
|
bool had_failures = suite_end(suite_failures);
|
|
176
256
|
print_suite_title("Processor Modes Tests", had_failures, false);
|
|
177
257
|
}
|
|
178
258
|
|
|
259
|
+
void test_quarto_mode(void) {
|
|
260
|
+
int suite_failures = suite_start();
|
|
261
|
+
print_suite_title("Quarto Mode Tests", false, true);
|
|
262
|
+
|
|
263
|
+
apex_options opts = apex_options_for_mode(APEX_MODE_QUARTO);
|
|
264
|
+
char *html;
|
|
265
|
+
|
|
266
|
+
const char *callout =
|
|
267
|
+
"::: {.callout-warning}\n"
|
|
268
|
+
"Warning body.\n"
|
|
269
|
+
"::: ";
|
|
270
|
+
html = apex_markdown_to_html(callout, strlen(callout), &opts);
|
|
271
|
+
assert_contains(html, "class=\"callout", "Quarto mode renders callout blocks");
|
|
272
|
+
assert_contains(html, "Warning body", "Quarto callout body preserved");
|
|
273
|
+
apex_free_string(html);
|
|
274
|
+
|
|
275
|
+
const char *fig_alt = "{fig-alt=\"Alt text\"}";
|
|
276
|
+
html = apex_markdown_to_html(fig_alt, strlen(fig_alt), &opts);
|
|
277
|
+
assert_contains(html, "<figure>", "fig-alt image wrapped in figure");
|
|
278
|
+
assert_contains(html, "<figcaption>Caption</figcaption>", "fig-alt uses markdown alt as caption");
|
|
279
|
+
assert_contains(html, "alt=\"Alt text\"", "fig-alt maps to img alt attribute");
|
|
280
|
+
assert_not_contains(html, "fig-alt=", "fig-alt attribute stripped from output");
|
|
281
|
+
apex_free_string(html);
|
|
282
|
+
|
|
283
|
+
/* Direct convert test: fig-alt rewrites img alt for accessibility */
|
|
284
|
+
{
|
|
285
|
+
const char *in = "<p><img src=\"elephant.png\" alt=\"Caption\" fig-alt=\"Alt text\" /></p>";
|
|
286
|
+
char *out = apex_convert_image_captions(in, true, false);
|
|
287
|
+
assert_contains(out, "alt=\"Alt text\"", "fig-alt maps to img alt attribute (direct convert)");
|
|
288
|
+
assert_contains(out, "<figcaption>Caption</figcaption>", "fig-alt caption from markdown alt (direct convert)");
|
|
289
|
+
free(out);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const char *div_block = "::: {.sidebar}\nSidebar content.\n:::";
|
|
293
|
+
html = apex_markdown_to_html(div_block, strlen(div_block), &opts);
|
|
294
|
+
assert_contains(html, "class=\"sidebar\"", "Quarto mode renders fenced divs");
|
|
295
|
+
apex_free_string(html);
|
|
296
|
+
|
|
297
|
+
const char *raw_html_block =
|
|
298
|
+
"Before\n\n"
|
|
299
|
+
"```{=html}\n"
|
|
300
|
+
"<strong>raw</strong>\n"
|
|
301
|
+
"```\n\n"
|
|
302
|
+
"After";
|
|
303
|
+
html = apex_markdown_to_html(raw_html_block, strlen(raw_html_block), &opts);
|
|
304
|
+
assert_contains(html, "<strong>raw</strong>", "raw {=html} block passthrough");
|
|
305
|
+
assert_not_contains(html, "**raw**", "raw {=html} block not markdown-processed");
|
|
306
|
+
assert_not_contains(html, "<code", "raw {=html} block not wrapped in code");
|
|
307
|
+
apex_free_string(html);
|
|
308
|
+
|
|
309
|
+
const char *raw_latex_block =
|
|
310
|
+
"```{=latex}\n"
|
|
311
|
+
"\\textbf{Bold}\n"
|
|
312
|
+
"```";
|
|
313
|
+
html = apex_markdown_to_html(raw_latex_block, strlen(raw_latex_block), &opts);
|
|
314
|
+
assert_contains(html, "<!-- raw format=latex -->", "raw {=latex} block wrapped in comment");
|
|
315
|
+
assert_contains(html, "\\textbf{Bold}", "raw {=latex} body preserved in comment");
|
|
316
|
+
assert_not_contains(html, "<code", "raw {=latex} block not wrapped in code");
|
|
317
|
+
apex_free_string(html);
|
|
318
|
+
|
|
319
|
+
const char *raw_html_inline = "Text `<em>inline</em>`{=html} end.";
|
|
320
|
+
html = apex_markdown_to_html(raw_html_inline, strlen(raw_html_inline), &opts);
|
|
321
|
+
assert_contains(html, "<em>inline</em>", "inline {=html} passthrough");
|
|
322
|
+
assert_not_contains(html, "`{=html}`", "inline {=html} marker stripped");
|
|
323
|
+
apex_free_string(html);
|
|
324
|
+
|
|
325
|
+
/* Unified mode without quarto extensions leaves {=html} fences intact */
|
|
326
|
+
apex_options unified_opts = apex_options_for_mode(APEX_MODE_UNIFIED);
|
|
327
|
+
unified_opts.enable_quarto_extensions = false;
|
|
328
|
+
html = apex_markdown_to_html("```{=html}\n<b>x</b>\n```", 18, &unified_opts);
|
|
329
|
+
assert_contains(html, "<code", "non-quarto mode keeps {=html} as code fence");
|
|
330
|
+
apex_free_string(html);
|
|
331
|
+
|
|
332
|
+
const char *example_lists =
|
|
333
|
+
"(@) My first example will be numbered (1).\n"
|
|
334
|
+
"(@) My second example will be numbered (2).\n";
|
|
335
|
+
html = apex_markdown_to_html(example_lists, strlen(example_lists), &opts);
|
|
336
|
+
assert_contains(html, "<ol>", "example list (@) produces ordered list");
|
|
337
|
+
assert_contains(html, "My first example", "example list first item");
|
|
338
|
+
assert_contains(html, "My second example", "example list second item");
|
|
339
|
+
assert_not_contains(html, "(@)", "example list marker stripped");
|
|
340
|
+
apex_free_string(html);
|
|
341
|
+
|
|
342
|
+
const char *example_lists_break =
|
|
343
|
+
"(@) My first example will be numbered (1).\n"
|
|
344
|
+
"(@) My second example will be numbered (2).\n"
|
|
345
|
+
"\n"
|
|
346
|
+
"Explanation of examples.\n"
|
|
347
|
+
"\n"
|
|
348
|
+
"(@) My third example will be numbered (3).\n";
|
|
349
|
+
html = apex_markdown_to_html(example_lists_break, strlen(example_lists_break), &opts);
|
|
350
|
+
assert_contains(html, "Explanation of examples", "example list keeps interruption text");
|
|
351
|
+
assert_contains(html, "My third example", "example list third item after break");
|
|
352
|
+
assert_contains(html, "start=\"3\"", "example list continues numbering after interruption");
|
|
353
|
+
assert_not_contains(html, "(@)", "example list break strips marker");
|
|
354
|
+
apex_free_string(html);
|
|
355
|
+
|
|
356
|
+
const char *example_lists_labeled =
|
|
357
|
+
"(@good) This is a good example.\n";
|
|
358
|
+
html = apex_markdown_to_html(example_lists_labeled, strlen(example_lists_labeled), &opts);
|
|
359
|
+
assert_contains(html, "This is a good example", "labeled example list item content");
|
|
360
|
+
assert_not_contains(html, "(@good)", "labeled example list marker stripped");
|
|
361
|
+
apex_free_string(html);
|
|
362
|
+
|
|
363
|
+
const char *roman_list =
|
|
364
|
+
"i) First\n"
|
|
365
|
+
"ii) Second\n"
|
|
366
|
+
"iii) Third\n";
|
|
367
|
+
html = apex_markdown_to_html(roman_list, strlen(roman_list), &opts);
|
|
368
|
+
assert_contains(html, "list-style-type: lower-roman", "roman list lower-roman style");
|
|
369
|
+
assert_contains(html, "First", "roman list first item");
|
|
370
|
+
assert_contains(html, "Second", "roman list second item");
|
|
371
|
+
assert_not_contains(html, "<!-- apex-alpha-list-", "roman list marker comment stripped");
|
|
372
|
+
apex_free_string(html);
|
|
373
|
+
|
|
374
|
+
const char *line_block =
|
|
375
|
+
"| Line one\n"
|
|
376
|
+
"| preserved spaces\n"
|
|
377
|
+
"| Line three\n";
|
|
378
|
+
html = apex_markdown_to_html(line_block, strlen(line_block), &opts);
|
|
379
|
+
assert_contains(html, "class=\"line-block\"", "line block wrapper");
|
|
380
|
+
assert_contains(html, "class=\"line\">Line one", "line block first line");
|
|
381
|
+
assert_contains(html, "class=\"line\"> preserved spaces", "line block preserves inner spaces");
|
|
382
|
+
assert_contains(html, "class=\"line\">Line three", "line block third line");
|
|
383
|
+
apex_free_string(html);
|
|
384
|
+
|
|
385
|
+
const char *code_fence_attrs =
|
|
386
|
+
"```{.python filename=\"run.py\"}\n"
|
|
387
|
+
"print(\"hello\")\n"
|
|
388
|
+
"```\n";
|
|
389
|
+
html = apex_markdown_to_html(code_fence_attrs, strlen(code_fence_attrs), &opts);
|
|
390
|
+
assert_contains(html, "data-filename=\"run.py\"", "code fence filename attribute on pre");
|
|
391
|
+
assert_contains(html, "print", "code fence body preserved");
|
|
392
|
+
assert_not_contains(html, "{.python", "code fence braced info string normalized");
|
|
393
|
+
assert_not_contains(html, "apex-code-fence-attrs", "code fence marker comment stripped");
|
|
394
|
+
apex_free_string(html);
|
|
395
|
+
|
|
396
|
+
const char *code_fence_linenos =
|
|
397
|
+
"```{.python linenos=true}\n"
|
|
398
|
+
"print(\"hi\")\n"
|
|
399
|
+
"```\n";
|
|
400
|
+
html = apex_markdown_to_html(code_fence_linenos, strlen(code_fence_linenos), &opts);
|
|
401
|
+
assert_contains(html, "data-linenos=\"true\"", "code fence linenos attribute on pre");
|
|
402
|
+
apex_free_string(html);
|
|
403
|
+
|
|
404
|
+
const char *mermaid_fence =
|
|
405
|
+
"```{mermaid}\n"
|
|
406
|
+
"flowchart LR\n"
|
|
407
|
+
" A --> B\n"
|
|
408
|
+
"```\n";
|
|
409
|
+
html = apex_markdown_to_html(mermaid_fence, strlen(mermaid_fence), &opts);
|
|
410
|
+
assert_contains(html, "<pre class=\"mermaid\">", "mermaid fence renders pre.mermaid");
|
|
411
|
+
assert_contains(html, "flowchart LR", "mermaid fence body preserved");
|
|
412
|
+
assert_contains(html, "A --> B", "mermaid fence diagram content preserved");
|
|
413
|
+
assert_not_contains(html, "{mermaid}", "mermaid fence marker stripped");
|
|
414
|
+
assert_not_contains(html, "<code", "mermaid fence not wrapped in code");
|
|
415
|
+
apex_free_string(html);
|
|
416
|
+
|
|
417
|
+
const char *dot_fence =
|
|
418
|
+
"```{dot}\n"
|
|
419
|
+
"digraph { A -> B; }\n"
|
|
420
|
+
"```\n";
|
|
421
|
+
html = apex_markdown_to_html(dot_fence, strlen(dot_fence), &opts);
|
|
422
|
+
assert_contains(html, "<pre class=\"graphviz\">", "dot fence renders pre.graphviz");
|
|
423
|
+
assert_contains(html, "digraph", "dot fence body preserved");
|
|
424
|
+
apex_free_string(html);
|
|
425
|
+
|
|
426
|
+
const char *graphviz_fence =
|
|
427
|
+
"```{graphviz}\n"
|
|
428
|
+
"graph { A -- B; }\n"
|
|
429
|
+
"```\n";
|
|
430
|
+
html = apex_markdown_to_html(graphviz_fence, strlen(graphviz_fence), &opts);
|
|
431
|
+
assert_contains(html, "<pre class=\"graphviz\">", "graphviz fence renders pre.graphviz");
|
|
432
|
+
assert_contains(html, "graph { A -- B", "graphviz fence body preserved");
|
|
433
|
+
apex_free_string(html);
|
|
434
|
+
|
|
435
|
+
apex_options no_diagrams_opts = apex_options_for_mode(APEX_MODE_QUARTO);
|
|
436
|
+
no_diagrams_opts.enable_quarto_diagrams = false;
|
|
437
|
+
html = apex_markdown_to_html(mermaid_fence, strlen(mermaid_fence), &no_diagrams_opts);
|
|
438
|
+
assert_contains(html, "<code", "disabled quarto diagrams keeps mermaid as code fence");
|
|
439
|
+
assert_not_contains(html, "class=\"mermaid\"", "disabled quarto diagrams skips pre.mermaid");
|
|
440
|
+
apex_free_string(html);
|
|
441
|
+
|
|
442
|
+
apex_options no_unsafe_opts = apex_options_for_mode(APEX_MODE_QUARTO);
|
|
443
|
+
no_unsafe_opts.unsafe = false;
|
|
444
|
+
html = apex_markdown_to_html(mermaid_fence, strlen(mermaid_fence), &no_unsafe_opts);
|
|
445
|
+
assert_contains(html, "<code", "no-unsafe keeps mermaid fence as code block");
|
|
446
|
+
assert_not_contains(html, "class=\"mermaid\"", "no-unsafe skips raw pre.mermaid");
|
|
447
|
+
apex_free_string(html);
|
|
448
|
+
|
|
449
|
+
apex_options standalone_opts = apex_options_for_mode(APEX_MODE_QUARTO);
|
|
450
|
+
standalone_opts.standalone = true;
|
|
451
|
+
html = apex_markdown_to_html(mermaid_fence, strlen(mermaid_fence), &standalone_opts);
|
|
452
|
+
assert_contains(html, "mermaid.min.js", "standalone quarto auto-injects mermaid script");
|
|
453
|
+
assert_contains(html, "</body>", "standalone mermaid output is full document");
|
|
454
|
+
apex_free_string(html);
|
|
455
|
+
|
|
456
|
+
apex_options standalone_with_script = apex_options_for_mode(APEX_MODE_QUARTO);
|
|
457
|
+
standalone_with_script.standalone = true;
|
|
458
|
+
standalone_with_script.script_tags = (char *[]){ strdup("<!-- mermaid already -->"), NULL };
|
|
459
|
+
html = apex_markdown_to_html(mermaid_fence, strlen(mermaid_fence), &standalone_with_script);
|
|
460
|
+
assert_not_contains(html, "mermaid.min.js", "standalone skips auto mermaid when script_tags mention mermaid");
|
|
461
|
+
assert_contains(html, "<!-- mermaid already -->", "standalone preserves user script_tags");
|
|
462
|
+
apex_free_string(html);
|
|
463
|
+
free(standalone_with_script.script_tags[0]);
|
|
464
|
+
|
|
465
|
+
const char *pagebreak_shortcode = "Page 1\n\n{{< pagebreak >}}\n\nPage 2";
|
|
466
|
+
html = apex_markdown_to_html(pagebreak_shortcode, strlen(pagebreak_shortcode), &opts);
|
|
467
|
+
assert_contains(html, "page-break-after", "pagebreak shortcode renders page break");
|
|
468
|
+
assert_contains(html, "Page 2", "content after pagebreak shortcode preserved");
|
|
469
|
+
assert_not_contains(html, "{{< pagebreak >}}", "pagebreak shortcode marker stripped");
|
|
470
|
+
apex_free_string(html);
|
|
471
|
+
|
|
472
|
+
const char *kbd_shortcode = "Press {{< kbd $@3 >}} to save.";
|
|
473
|
+
html = apex_markdown_to_html(kbd_shortcode, strlen(kbd_shortcode), &opts);
|
|
474
|
+
assert_contains(html, "{% kbd $@3 %}", "kbd shortcode converts to liquid tag");
|
|
475
|
+
assert_not_contains(html, "{{< kbd", "kbd shortcode marker stripped");
|
|
476
|
+
apex_free_string(html);
|
|
477
|
+
|
|
478
|
+
apex_options include_opts = apex_options_for_mode(APEX_MODE_QUARTO);
|
|
479
|
+
include_opts.enable_file_includes = true;
|
|
480
|
+
#ifdef TEST_FIXTURES_DIR
|
|
481
|
+
include_opts.base_directory = TEST_FIXTURES_DIR;
|
|
482
|
+
#else
|
|
483
|
+
include_opts.base_directory = "tests/fixtures/includes";
|
|
484
|
+
#endif
|
|
485
|
+
const char *include_shortcode = "Before\n\n{{< include simple.md >}}\n\nAfter";
|
|
486
|
+
html = apex_markdown_to_html(include_shortcode, strlen(include_shortcode), &include_opts);
|
|
487
|
+
assert_contains(html, "Included Content", "include shortcode expands file content");
|
|
488
|
+
assert_contains(html, "After", "content after include shortcode preserved");
|
|
489
|
+
assert_not_contains(html, "{{< include", "include shortcode marker stripped");
|
|
490
|
+
apex_free_string(html);
|
|
491
|
+
|
|
492
|
+
apex_options no_shortcodes_opts = apex_options_for_mode(APEX_MODE_QUARTO);
|
|
493
|
+
no_shortcodes_opts.enable_quarto_shortcodes = false;
|
|
494
|
+
html = apex_markdown_to_html(pagebreak_shortcode, strlen(pagebreak_shortcode), &no_shortcodes_opts);
|
|
495
|
+
assert_contains(html, "{{< pagebreak", "disabled quarto shortcodes leave marker in output");
|
|
496
|
+
assert_not_contains(html, "page-break-after", "disabled quarto shortcodes skip pagebreak expansion");
|
|
497
|
+
apex_free_string(html);
|
|
498
|
+
|
|
499
|
+
const char *unknown_shortcode = "Text {{< widget foo >}} end.";
|
|
500
|
+
html = apex_markdown_to_html(unknown_shortcode, strlen(unknown_shortcode), &opts);
|
|
501
|
+
assert_contains(html, "{{< widget foo", "unknown shortcode left unchanged");
|
|
502
|
+
apex_free_string(html);
|
|
503
|
+
|
|
504
|
+
const char *empty_div_reset =
|
|
505
|
+
"1. First\n\n::: {}\n\n1. Second\n";
|
|
506
|
+
html = apex_markdown_to_html(empty_div_reset, strlen(empty_div_reset), &opts);
|
|
507
|
+
assert_contains(html, "<ol>", "empty div list reset produces ordered lists");
|
|
508
|
+
{
|
|
509
|
+
const char *first_ol = strstr(html, "<ol>");
|
|
510
|
+
const char *second_ol = first_ol ? strstr(first_ol + 1, "<ol>") : NULL;
|
|
511
|
+
test_result(second_ol != NULL, "empty div separates two ordered lists");
|
|
512
|
+
}
|
|
513
|
+
apex_free_string(html);
|
|
514
|
+
|
|
515
|
+
const char *cross_ref = "See @fig-elephant and @sec-intro for details.";
|
|
516
|
+
html = apex_markdown_to_html(cross_ref, strlen(cross_ref), &opts);
|
|
517
|
+
assert_contains(html, "<span class=\"quarto-xref\">@fig-elephant</span>", "fig cross-ref wrapped");
|
|
518
|
+
assert_contains(html, "<span class=\"quarto-xref\">@sec-intro</span>", "sec cross-ref wrapped");
|
|
519
|
+
apex_free_string(html);
|
|
520
|
+
|
|
521
|
+
{
|
|
522
|
+
apex_options cite_opts = apex_options_for_mode(APEX_MODE_QUARTO);
|
|
523
|
+
cite_opts.base_directory = "tests";
|
|
524
|
+
const char *bib_files[] = { "test_refs.bib", NULL };
|
|
525
|
+
cite_opts.bibliography_files = (char **)bib_files;
|
|
526
|
+
const char *mixed_cite_xref =
|
|
527
|
+
"See [@doe99] and @fig-elephant.";
|
|
528
|
+
html = apex_markdown_to_html(mixed_cite_xref, strlen(mixed_cite_xref), &cite_opts);
|
|
529
|
+
assert_contains(html, "citation", "bracket citation processed with bibliography");
|
|
530
|
+
assert_contains(html, "doe99", "bracket citation key preserved with bibliography");
|
|
531
|
+
assert_contains(html, "<span class=\"quarto-xref\">@fig-elephant</span>",
|
|
532
|
+
"bare fig xref not parsed as citation when bibliography set");
|
|
533
|
+
assert_not_contains(html, "data-cites=\"fig-elephant\"", "fig xref not in citation data-cites");
|
|
534
|
+
apex_free_string(html);
|
|
535
|
+
|
|
536
|
+
const char *bracket_fig = "See [@fig-elephant].";
|
|
537
|
+
html = apex_markdown_to_html(bracket_fig, strlen(bracket_fig), &cite_opts);
|
|
538
|
+
assert_contains(html, "citation", "bracketed @fig- key still processed as citation");
|
|
539
|
+
assert_contains(html, "fig-elephant", "bracketed fig- citation key preserved");
|
|
540
|
+
apex_free_string(html);
|
|
541
|
+
|
|
542
|
+
apex_options no_xref_opts = cite_opts;
|
|
543
|
+
no_xref_opts.enable_quarto_xrefs = false;
|
|
544
|
+
const char *bare_fig = "See @fig-elephant.";
|
|
545
|
+
html = apex_markdown_to_html(bare_fig, strlen(bare_fig), &no_xref_opts);
|
|
546
|
+
assert_contains(html, "citation", "bare @fig- treated as citation when quarto-xrefs off");
|
|
547
|
+
assert_not_contains(html, "quarto-xref", "no xref wrapper when quarto-xrefs off");
|
|
548
|
+
apex_free_string(html);
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
apex_options hidden_opts = apex_options_for_mode(APEX_MODE_QUARTO);
|
|
552
|
+
hidden_opts.standalone = true;
|
|
553
|
+
html = apex_markdown_to_html("::: {.hidden}\nHidden content.\n:::", 35, &hidden_opts);
|
|
554
|
+
assert_contains(html, "class=\"hidden\"", "hidden div class preserved");
|
|
555
|
+
assert_contains(html, ".hidden { display: none; }", "standalone quarto CSS hides .hidden");
|
|
556
|
+
apex_free_string(html);
|
|
557
|
+
|
|
558
|
+
apex_options unified_opts2 = apex_options_for_mode(APEX_MODE_UNIFIED);
|
|
559
|
+
unified_opts2.enable_quarto_extensions = false;
|
|
560
|
+
html = apex_markdown_to_html("```{.python}\nx\n```", 18, &unified_opts2);
|
|
561
|
+
assert_contains(html, "{.python}", "non-quarto mode keeps braced fence info string");
|
|
562
|
+
apex_free_string(html);
|
|
563
|
+
|
|
564
|
+
bool had_failures = suite_end(suite_failures);
|
|
565
|
+
print_suite_title("Quarto Mode Tests", had_failures, false);
|
|
566
|
+
}
|
|
567
|
+
|
|
179
568
|
/**
|
|
180
569
|
* Test cmark_init callback for custom extension registration
|
|
181
570
|
*/
|
|
@@ -401,6 +790,87 @@ void test_multimarkdown_image_attributes(void) {
|
|
|
401
790
|
apex_free_string(html);
|
|
402
791
|
}
|
|
403
792
|
|
|
793
|
+
/* Picture conversion should preserve image IAL attrs on fallback <img> */
|
|
794
|
+
{
|
|
795
|
+
apex_options opts = apex_options_for_mode(APEX_MODE_UNIFIED);
|
|
796
|
+
const char *picture_ial_md =
|
|
797
|
+
"[{:loading=lazy width=400 height=400}]"
|
|
798
|
+
"(img/text-basic@2x.jpg){.widget-shot.js-lightbox}";
|
|
799
|
+
char *html = apex_markdown_to_html(picture_ial_md, strlen(picture_ial_md), &opts);
|
|
800
|
+
assert_contains(html, "<a href=\"img/text-basic@2x.jpg\" class=\"widget-shot js-lightbox\">",
|
|
801
|
+
"picture+ial: outer link classes preserved");
|
|
802
|
+
assert_contains(html, "<picture>", "picture+ial: picture element");
|
|
803
|
+
assert_contains(html, "img/text-basic.avif 1x, img/text-basic@2x.avif 2x",
|
|
804
|
+
"picture+ial: avif srcset with @2x");
|
|
805
|
+
assert_contains(html,
|
|
806
|
+
"<img src=\"img/text-basic.jpg\" alt=\"text-basic widget screenshot\" width=\"400\" height=\"400\" loading=\"lazy\">",
|
|
807
|
+
"picture+ial: fallback img preserves IAL attrs");
|
|
808
|
+
apex_free_string(html);
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
/* Raw HTML <a> must not steal/drop later markdown link IAL classes */
|
|
812
|
+
{
|
|
813
|
+
apex_options opts = apex_options_for_mode(APEX_MODE_UNIFIED);
|
|
814
|
+
opts.enable_autolink = false;
|
|
815
|
+
opts.enable_image_captions = false;
|
|
816
|
+
const char *md =
|
|
817
|
+
"<div class=\"note\">\n"
|
|
818
|
+
"<p>See <a href=\"/integrations#getting-started\">Integrations</a>.</p>\n"
|
|
819
|
+
"</div>\n\n"
|
|
820
|
+
"- [{:loading=lazy width=400 height=400}]"
|
|
821
|
+
"(/x@2x.jpg){.widget-shot.js-lightbox}\n"
|
|
822
|
+
"{.g}\n";
|
|
823
|
+
char *html = apex_markdown_to_html(md, strlen(md), &opts);
|
|
824
|
+
assert_contains(html,
|
|
825
|
+
"<a href=\"/x@2x.jpg\" class=\"widget-shot js-lightbox\">",
|
|
826
|
+
"raw html <a> must not steal/drop later link IAL classes");
|
|
827
|
+
assert_contains(html,
|
|
828
|
+
"href=\"/integrations#getting-started\">Integrations</a>",
|
|
829
|
+
"raw html callout link remains unchanged");
|
|
830
|
+
assert_not_contains(html,
|
|
831
|
+
"href=\"/integrations#getting-started\" class=\"widget-shot",
|
|
832
|
+
"IAL must not be injected onto the raw html <a>");
|
|
833
|
+
apex_free_string(html);
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
/* Distinct href IALs after a raw HTML <a> keep correct classes */
|
|
837
|
+
{
|
|
838
|
+
apex_options opts = apex_options_for_mode(APEX_MODE_UNIFIED);
|
|
839
|
+
opts.enable_autolink = false;
|
|
840
|
+
const char *md =
|
|
841
|
+
"<p><a href=\"/raw\">Raw</a></p>\n\n"
|
|
842
|
+
"[One](/one){.class-one}\n\n"
|
|
843
|
+
"[Two](/two){.class-two}\n";
|
|
844
|
+
char *html = apex_markdown_to_html(md, strlen(md), &opts);
|
|
845
|
+
assert_contains(html, "<a href=\"/one\" class=\"class-one\">",
|
|
846
|
+
"distinct href after raw <a>: first link IAL");
|
|
847
|
+
assert_contains(html, "<a href=\"/two\" class=\"class-two\">",
|
|
848
|
+
"distinct href after raw <a>: second link IAL");
|
|
849
|
+
assert_not_contains(html, "href=\"/raw\" class=",
|
|
850
|
+
"distinct href after raw <a>: raw link unchanged");
|
|
851
|
+
apex_free_string(html);
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
/* Same-href markdown links after raw HTML <a>: disambiguate by order */
|
|
855
|
+
{
|
|
856
|
+
apex_options opts = apex_options_for_mode(APEX_MODE_UNIFIED);
|
|
857
|
+
opts.enable_autolink = false;
|
|
858
|
+
const char *md =
|
|
859
|
+
"<p><a href=\"/same\">Raw</a></p>\n\n"
|
|
860
|
+
"[First](/same){.first}\n\n"
|
|
861
|
+
"[Second](/same){.second}\n";
|
|
862
|
+
char *html = apex_markdown_to_html(md, strlen(md), &opts);
|
|
863
|
+
assert_contains(html, "<a href=\"/same\" class=\"first\">First</a>",
|
|
864
|
+
"same href after raw <a>: first markdown link IAL");
|
|
865
|
+
assert_contains(html, "<a href=\"/same\" class=\"second\">Second</a>",
|
|
866
|
+
"same href after raw <a>: second markdown link IAL");
|
|
867
|
+
assert_not_contains(html, "href=\"/same\" class=\"first\">Raw",
|
|
868
|
+
"same href after raw <a>: raw link not given first IAL");
|
|
869
|
+
assert_not_contains(html, "href=\"/same\" class=\"second\">Raw",
|
|
870
|
+
"same href after raw <a>: raw link not given second IAL");
|
|
871
|
+
apex_free_string(html);
|
|
872
|
+
}
|
|
873
|
+
|
|
404
874
|
/* Video URL:  emits <video> */
|
|
405
875
|
{
|
|
406
876
|
apex_options opts = apex_options_for_mode(APEX_MODE_UNIFIED);
|
|
@@ -566,6 +1036,21 @@ void test_file_includes(void) {
|
|
|
566
1036
|
assert_contains(html, "Included Content", "MMD transclusion works");
|
|
567
1037
|
apex_free_string(html);
|
|
568
1038
|
|
|
1039
|
+
apex_options plugin_opts = opts;
|
|
1040
|
+
plugin_opts.enable_plugins = true;
|
|
1041
|
+
plugin_opts.allow_external_plugin_detection = false;
|
|
1042
|
+
plugin_opts.plugin_register = include_preparse_plugin_register;
|
|
1043
|
+
html = apex_markdown_to_html("Include: {{simple.md}}", 22, &plugin_opts);
|
|
1044
|
+
assert_contains(html, "Plugin Included Content", "Included file runs through pre-parse plugin pipeline");
|
|
1045
|
+
apex_free_string(html);
|
|
1046
|
+
|
|
1047
|
+
const char *plugin_section_include = "![[sections#Section 2]]";
|
|
1048
|
+
html = apex_markdown_to_html(plugin_section_include, strlen(plugin_section_include), &plugin_opts);
|
|
1049
|
+
assert_contains(html, "Plugin Section 2", "Section include resolves before plugin rewrites section heading");
|
|
1050
|
+
assert_contains(html, "Nested section 2.1 text.", "Section include with plugin keeps nested subsection");
|
|
1051
|
+
assert_not_contains(html, "Section 3 text.", "Section include with plugin still stops at next same-level heading");
|
|
1052
|
+
apex_free_string(html);
|
|
1053
|
+
|
|
569
1054
|
/* Test MMD wildcard transclusion: file.* (legacy behavior) */
|
|
570
1055
|
html = apex_markdown_to_html("Include: {{simple.*}}", 22, &opts);
|
|
571
1056
|
assert_contains(html, "Included Content", "MMD wildcard file.* resolves to simple.md");
|
|
@@ -697,6 +1182,63 @@ void test_file_includes(void) {
|
|
|
697
1182
|
assert_not_contains(html, "Included Content", "Line range excludes header");
|
|
698
1183
|
apex_free_string(html);
|
|
699
1184
|
|
|
1185
|
+
/* Section extraction parity across include syntaxes */
|
|
1186
|
+
const char *mmd_section_include = "{{sections.md#Section 2}}";
|
|
1187
|
+
html = apex_markdown_to_html(mmd_section_include, strlen(mmd_section_include), &opts);
|
|
1188
|
+
assert_contains(html, "Section 2 text.", "MMD section include: selected section content");
|
|
1189
|
+
assert_contains(html, "Nested section 2.1 text.", "MMD section include: nested subsection included");
|
|
1190
|
+
assert_not_contains(html, "Section 3 text.", "MMD section include: stops before next same-level heading");
|
|
1191
|
+
apex_free_string(html);
|
|
1192
|
+
|
|
1193
|
+
const char *marked_section_include = "<<[sections.md#Section 2]";
|
|
1194
|
+
html = apex_markdown_to_html(marked_section_include, strlen(marked_section_include), &opts);
|
|
1195
|
+
assert_contains(html, "Section 2 text.", "Marked section include: selected section content");
|
|
1196
|
+
assert_contains(html, "Nested section 2.1 text.", "Marked section include: nested subsection included");
|
|
1197
|
+
assert_not_contains(html, "Section 3 text.", "Marked section include: stops before next same-level heading");
|
|
1198
|
+
apex_free_string(html);
|
|
1199
|
+
|
|
1200
|
+
const char *ia_section_include = "/sections.md#Section 2";
|
|
1201
|
+
html = apex_markdown_to_html(ia_section_include, strlen(ia_section_include), &opts);
|
|
1202
|
+
assert_contains(html, "Section 2 text.", "iA Writer section include: selected section content");
|
|
1203
|
+
assert_contains(html, "Nested section 2.1 text.", "iA Writer section include: nested subsection included");
|
|
1204
|
+
assert_not_contains(html, "Section 3 text.", "iA Writer section include: stops before next same-level heading");
|
|
1205
|
+
apex_free_string(html);
|
|
1206
|
+
|
|
1207
|
+
const char *obsidian_section_include = "![[sections#Section 2]]";
|
|
1208
|
+
html = apex_markdown_to_html(obsidian_section_include, strlen(obsidian_section_include), &opts);
|
|
1209
|
+
assert_contains(html, "Section 2 text.", "Obsidian embed include: selected section content");
|
|
1210
|
+
assert_contains(html, "Nested section 2.1 text.", "Obsidian embed include: nested subsection included");
|
|
1211
|
+
assert_not_contains(html, "Section 3 text.", "Obsidian embed include: stops before next same-level heading");
|
|
1212
|
+
apex_free_string(html);
|
|
1213
|
+
|
|
1214
|
+
opts.wikilink_extension = "txt";
|
|
1215
|
+
html = apex_markdown_to_html(obsidian_section_include, strlen(obsidian_section_include), &opts);
|
|
1216
|
+
assert_contains(html, "Section 2 text from TXT fixture.", "Obsidian embed include: uses --wikilink-extension default");
|
|
1217
|
+
assert_not_contains(html, "Nested section 2.1 text.", "Obsidian embed include: extension override changes source file");
|
|
1218
|
+
assert_not_contains(html, "Section 3 text from TXT fixture.", "Obsidian embed include: extension override still trims to section");
|
|
1219
|
+
apex_free_string(html);
|
|
1220
|
+
opts.wikilink_extension = ".txt";
|
|
1221
|
+
html = apex_markdown_to_html(obsidian_section_include, strlen(obsidian_section_include), &opts);
|
|
1222
|
+
assert_contains(html, "Section 2 text from TXT fixture.", "Obsidian embed include: dot-prefixed wikilink extension works");
|
|
1223
|
+
apex_free_string(html);
|
|
1224
|
+
opts.wikilink_extension = "html";
|
|
1225
|
+
html = apex_markdown_to_html(obsidian_section_include, strlen(obsidian_section_include), &opts);
|
|
1226
|
+
assert_contains(html, "Section 2 text.", "Obsidian embed include: falls back to .md when configured extension file is missing");
|
|
1227
|
+
apex_free_string(html);
|
|
1228
|
+
opts.wikilink_extension = NULL;
|
|
1229
|
+
|
|
1230
|
+
const char *missing_section_obsidian = "![[sections#Does Not Exist]]";
|
|
1231
|
+
html = apex_markdown_to_html(missing_section_obsidian, strlen(missing_section_obsidian), &opts);
|
|
1232
|
+
assert_contains(html, "Intro paragraph.", "Obsidian embed include: missing section falls back to full document");
|
|
1233
|
+
assert_contains(html, "Section 3 text.", "Obsidian embed include: full document fallback includes later sections");
|
|
1234
|
+
apex_free_string(html);
|
|
1235
|
+
|
|
1236
|
+
const char *missing_section_mmd = "{{sections.md#Does Not Exist}}";
|
|
1237
|
+
html = apex_markdown_to_html(missing_section_mmd, strlen(missing_section_mmd), &opts);
|
|
1238
|
+
assert_contains(html, "Intro paragraph.", "MMD include: missing section falls back to full document");
|
|
1239
|
+
assert_contains(html, "Section 3 text.", "MMD include: full document fallback includes later sections");
|
|
1240
|
+
apex_free_string(html);
|
|
1241
|
+
|
|
700
1242
|
/* Test Marked code include with address syntax */
|
|
701
1243
|
html = apex_markdown_to_html("<<(code.py)[1,3]", 18, &opts);
|
|
702
1244
|
assert_contains(html, "def hello()", "Code include with line range");
|
|
@@ -711,7 +1253,7 @@ void test_file_includes(void) {
|
|
|
711
1253
|
assert_not_contains(html, "Included Content", "Regex range excludes before first match");
|
|
712
1254
|
apex_free_string(html);
|
|
713
1255
|
|
|
714
|
-
/*
|
|
1256
|
+
/* iA Writer plain include still includes full file when no section is requested */
|
|
715
1257
|
html = apex_markdown_to_html("/code.py", 8, &opts);
|
|
716
1258
|
assert_contains(html, "def hello()", "iA Writer syntax unchanged");
|
|
717
1259
|
assert_contains(html, "return True", "iA Writer includes full file");
|
|
@@ -927,6 +1469,107 @@ void test_definition_lists(void) {
|
|
|
927
1469
|
assert_not_contains(html, "<dt>term</dt>", "No definition list from multi-line inline code");
|
|
928
1470
|
apex_free_string(html);
|
|
929
1471
|
|
|
1472
|
+
/* Leanpub {::pagebreak /} must not trigger definition list reordering */
|
|
1473
|
+
const char *heading_pagebreak = "## Page breaks\n {::pagebreak /}";
|
|
1474
|
+
html = apex_markdown_to_html(heading_pagebreak, strlen(heading_pagebreak), &opts);
|
|
1475
|
+
assert_contains(html, "<h2", "Heading before indented Leanpub pagebreak marker");
|
|
1476
|
+
assert_contains(html, "Page", "Heading text preserved");
|
|
1477
|
+
assert_contains(html, "<pre><code>{::pagebreak /}", "Leanpub pagebreak marker in code block");
|
|
1478
|
+
assert_not_contains(html, "<dl>", "Leanpub pagebreak marker did not create definition list");
|
|
1479
|
+
const char *h2_pos = strstr(html, "<h2");
|
|
1480
|
+
const char *pre_pos = strstr(html, "<pre>");
|
|
1481
|
+
if (h2_pos && pre_pos && h2_pos < pre_pos) {
|
|
1482
|
+
test_result(true, "Heading appears before code block with Leanpub pagebreak marker");
|
|
1483
|
+
} else {
|
|
1484
|
+
test_result(false, "Heading should appear before code block with Leanpub pagebreak marker");
|
|
1485
|
+
}
|
|
1486
|
+
apex_free_string(html);
|
|
1487
|
+
|
|
1488
|
+
/* Buffered term before fenced code block must not be discarded */
|
|
1489
|
+
const char *term_before_code =
|
|
1490
|
+
"You can now use these environment variables in your Run Command actions:\n\n"
|
|
1491
|
+
"```bash\n"
|
|
1492
|
+
"echo hello\n"
|
|
1493
|
+
"```";
|
|
1494
|
+
html = apex_markdown_to_html(term_before_code, strlen(term_before_code), &opts);
|
|
1495
|
+
assert_contains(html, "Run Command actions", "Intro paragraph preserved before code block");
|
|
1496
|
+
{
|
|
1497
|
+
const char *intro_pos = strstr(html, "Run Command actions");
|
|
1498
|
+
const char *code_pos = strstr(html, "<pre");
|
|
1499
|
+
test_result(intro_pos && code_pos && intro_pos < code_pos,
|
|
1500
|
+
"Intro paragraph appears before fenced code block");
|
|
1501
|
+
}
|
|
1502
|
+
apex_free_string(html);
|
|
1503
|
+
|
|
1504
|
+
/* Fence immediately after definition list must become a real code block */
|
|
1505
|
+
{
|
|
1506
|
+
const char *dl_then_fence =
|
|
1507
|
+
"Term\n"
|
|
1508
|
+
": Def\n"
|
|
1509
|
+
"\n"
|
|
1510
|
+
"```\n"
|
|
1511
|
+
"code\n"
|
|
1512
|
+
"```\n";
|
|
1513
|
+
html = apex_markdown_to_html(dl_then_fence, strlen(dl_then_fence), &opts);
|
|
1514
|
+
assert_contains(html, "<dl>", "DL before bare fence still renders");
|
|
1515
|
+
assert_contains(html, "<pre", "Bare fence after DL becomes pre/code");
|
|
1516
|
+
assert_contains(html, "<code", "Bare fence after DL contains code element");
|
|
1517
|
+
assert_not_contains(html, "</dl>\n```", "Fence after DL is not left as raw backticks");
|
|
1518
|
+
apex_free_string(html);
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1521
|
+
/* Paragraph between DL and fence stays before the fence */
|
|
1522
|
+
{
|
|
1523
|
+
const char *dl_para_fence =
|
|
1524
|
+
"Term\n"
|
|
1525
|
+
": Def\n"
|
|
1526
|
+
"\n"
|
|
1527
|
+
"This should stay before the diagram\n"
|
|
1528
|
+
"\n"
|
|
1529
|
+
"```mermaid\n"
|
|
1530
|
+
"graph TD\n"
|
|
1531
|
+
" A --> B\n"
|
|
1532
|
+
"```\n";
|
|
1533
|
+
html = apex_markdown_to_html(dl_para_fence, strlen(dl_para_fence), &opts);
|
|
1534
|
+
const char *para = strstr(html, "This should stay before the diagram");
|
|
1535
|
+
const char *pre = strstr(html, "<pre");
|
|
1536
|
+
test_result(para && pre && para < pre,
|
|
1537
|
+
"Paragraph between DL and fence stays before pre");
|
|
1538
|
+
apex_free_string(html);
|
|
1539
|
+
}
|
|
1540
|
+
|
|
1541
|
+
/* DL + callout + following paragraph: blanks must not be eaten */
|
|
1542
|
+
{
|
|
1543
|
+
apex_options callout_opts = apex_options_default();
|
|
1544
|
+
callout_opts.mode = APEX_MODE_UNIFIED;
|
|
1545
|
+
callout_opts.enable_callouts = true;
|
|
1546
|
+
callout_opts.enable_definition_lists = true;
|
|
1547
|
+
const char *dl_callout =
|
|
1548
|
+
"Term\n"
|
|
1549
|
+
": Def\n"
|
|
1550
|
+
"\n"
|
|
1551
|
+
"> [!Note]\n"
|
|
1552
|
+
"> note body\n"
|
|
1553
|
+
"\n"
|
|
1554
|
+
"After callout\n";
|
|
1555
|
+
html = apex_markdown_to_html(dl_callout, strlen(dl_callout), &callout_opts);
|
|
1556
|
+
assert_contains(html, "callout", "Callout after DL still renders");
|
|
1557
|
+
assert_contains(html, "After callout", "Paragraph after callout preserved");
|
|
1558
|
+
/* "After callout" must not sit inside the callout content blockquote */
|
|
1559
|
+
const char *after = strstr(html, "After callout");
|
|
1560
|
+
const char *callout_end = strstr(html, "</div>\n</div>");
|
|
1561
|
+
if (!callout_end) callout_end = strstr(html, "callout-content");
|
|
1562
|
+
test_result(after != NULL, "After callout text present in output");
|
|
1563
|
+
if (after && strstr(html, "callout-content")) {
|
|
1564
|
+
/* Find the callout-content section and ensure After is outside it */
|
|
1565
|
+
const char *content = strstr(html, "callout-content");
|
|
1566
|
+
const char *content_close = content ? strstr(content, "</div>") : NULL;
|
|
1567
|
+
test_result(content_close && after > content_close,
|
|
1568
|
+
"After callout is outside callout-content");
|
|
1569
|
+
}
|
|
1570
|
+
apex_free_string(html);
|
|
1571
|
+
}
|
|
1572
|
+
|
|
930
1573
|
bool had_failures = suite_end(suite_failures);
|
|
931
1574
|
print_suite_title("Definition Lists Tests", had_failures, false);
|
|
932
1575
|
}
|
|
@@ -994,6 +1637,68 @@ void test_callouts(void) {
|
|
|
994
1637
|
}
|
|
995
1638
|
apex_free_string(html);
|
|
996
1639
|
|
|
1640
|
+
/* Python-Markdown callouts are disabled by default */
|
|
1641
|
+
const char *py_callout = "!!! note \"Py Title\"\n Py body line";
|
|
1642
|
+
html = apex_markdown_to_html(py_callout, strlen(py_callout), &opts);
|
|
1643
|
+
assert_not_contains(html, "class=\"callout", "Python callout syntax ignored when flag disabled");
|
|
1644
|
+
apex_free_string(html);
|
|
1645
|
+
|
|
1646
|
+
/* Python-Markdown callouts enabled behind flag */
|
|
1647
|
+
opts.enable_py_callouts = true;
|
|
1648
|
+
html = apex_markdown_to_html(py_callout, strlen(py_callout), &opts);
|
|
1649
|
+
assert_contains(html, "class=\"callout", "Python callout recognized when enabled");
|
|
1650
|
+
assert_contains(html, "callout-note", "Python note callout type");
|
|
1651
|
+
assert_contains(html, "Py Title", "Python callout title preserved");
|
|
1652
|
+
apex_free_string(html);
|
|
1653
|
+
opts.enable_py_callouts = false;
|
|
1654
|
+
|
|
1655
|
+
/* markdown-callouts NOTE: syntax enabled behind py flag */
|
|
1656
|
+
opts.enable_py_callouts = true;
|
|
1657
|
+
html = apex_markdown_to_html("NOTE: Inline py callout body", 28, &opts);
|
|
1658
|
+
assert_contains(html, "class=\"callout", "NOTE: syntax recognized with py-callouts");
|
|
1659
|
+
assert_contains(html, "callout-note", "NOTE: maps to note callout");
|
|
1660
|
+
assert_contains(html, "Inline py callout body", "NOTE: body preserved");
|
|
1661
|
+
apex_free_string(html);
|
|
1662
|
+
|
|
1663
|
+
/* markdown-callouts collapsed syntax */
|
|
1664
|
+
const char *collapsed_py = ">? NOTE: Collapsed note\n> Collapsed body";
|
|
1665
|
+
html = apex_markdown_to_html(collapsed_py, strlen(collapsed_py), &opts);
|
|
1666
|
+
assert_contains(html, "<details", ">? syntax creates collapsible callout");
|
|
1667
|
+
assert_contains(html, "callout-note", ">? NOTE maps to note callout");
|
|
1668
|
+
assert_contains(html, "Collapsed body", ">? NOTE body preserved");
|
|
1669
|
+
apex_free_string(html);
|
|
1670
|
+
|
|
1671
|
+
opts.enable_py_callouts = false;
|
|
1672
|
+
|
|
1673
|
+
/* Quarto callouts are disabled by default */
|
|
1674
|
+
const char *quarto_callout =
|
|
1675
|
+
"::: {.callout-warning}\n"
|
|
1676
|
+
"## Quarto Title\n"
|
|
1677
|
+
"Quarto warning body.\n"
|
|
1678
|
+
":::\n";
|
|
1679
|
+
html = apex_markdown_to_html(quarto_callout, strlen(quarto_callout), &opts);
|
|
1680
|
+
assert_not_contains(html, "class=\"callout callout-warning\"", "Quarto callout syntax ignored when flag disabled");
|
|
1681
|
+
apex_free_string(html);
|
|
1682
|
+
|
|
1683
|
+
/* Quarto callouts enabled behind flag */
|
|
1684
|
+
opts.enable_quarto_callouts = true;
|
|
1685
|
+
html = apex_markdown_to_html(quarto_callout, strlen(quarto_callout), &opts);
|
|
1686
|
+
assert_contains(html, "class=\"callout", "Quarto callout recognized when enabled");
|
|
1687
|
+
assert_contains(html, "callout-warning", "Quarto warning callout type");
|
|
1688
|
+
assert_contains(html, "Quarto warning body", "Quarto callout body preserved");
|
|
1689
|
+
apex_free_string(html);
|
|
1690
|
+
|
|
1691
|
+
/* Quarto flag should still allow non-callout divs to render as divs */
|
|
1692
|
+
const char *regular_div =
|
|
1693
|
+
"::: {.sidebar}\n"
|
|
1694
|
+
"Regular div content.\n"
|
|
1695
|
+
":::\n";
|
|
1696
|
+
html = apex_markdown_to_html(regular_div, strlen(regular_div), &opts);
|
|
1697
|
+
assert_contains(html, "<div class=\"sidebar\"", "Non-callout fenced div still rendered with Quarto mode enabled");
|
|
1698
|
+
assert_not_contains(html, "class=\"callout", "Non-callout fenced div not converted into callout");
|
|
1699
|
+
apex_free_string(html);
|
|
1700
|
+
opts.enable_quarto_callouts = false;
|
|
1701
|
+
|
|
997
1702
|
bool had_failures = suite_end(suite_failures);
|
|
998
1703
|
print_suite_title("Callouts Tests", had_failures, false);
|
|
999
1704
|
}
|
|
@@ -1063,6 +1768,28 @@ void test_blockquote_lists(void) {
|
|
|
1063
1768
|
/* Definition lists may or may not be parsed depending on mode */
|
|
1064
1769
|
apex_free_string(html);
|
|
1065
1770
|
|
|
1771
|
+
/* Reference image after blockquote without blank line should not stay in blockquote */
|
|
1772
|
+
const char *ref_img_after_quote =
|
|
1773
|
+
"> Block quote\n"
|
|
1774
|
+
"![][2]\n"
|
|
1775
|
+
"\n"
|
|
1776
|
+
"[2]: /path/to/image.jpg\n"
|
|
1777
|
+
"\n"
|
|
1778
|
+
"Additional paragraph.";
|
|
1779
|
+
html = apex_markdown_to_html(ref_img_after_quote, strlen(ref_img_after_quote), &opts);
|
|
1780
|
+
assert_contains(html, "<blockquote>", "Blockquote present for ref image test");
|
|
1781
|
+
assert_contains(html, "Block quote", "Blockquote text preserved");
|
|
1782
|
+
assert_not_contains(html, "<blockquote>\n<p>Block quote\n<img", "Reference image not lazy-continued into blockquote");
|
|
1783
|
+
assert_contains(html, "<img src=\"/path/to/image.jpg\"", "Reference image resolved outside blockquote");
|
|
1784
|
+
assert_contains(html, "Additional paragraph.", "Paragraph after reference image preserved");
|
|
1785
|
+
apex_free_string(html);
|
|
1786
|
+
|
|
1787
|
+
/* Inline image after blockquote without blank line */
|
|
1788
|
+
html = apex_markdown_to_html("> Block quote\n\n\nNext paragraph.", 52, &opts);
|
|
1789
|
+
assert_not_contains(html, "<blockquote>\n<p>Block quote\n<", "Inline image not inside blockquote paragraph");
|
|
1790
|
+
assert_contains(html, "test.png", "Inline image rendered outside blockquote");
|
|
1791
|
+
apex_free_string(html);
|
|
1792
|
+
|
|
1066
1793
|
bool had_failures = suite_end(suite_failures);
|
|
1067
1794
|
print_suite_title("Blockquote Lists Tests", had_failures, false);
|
|
1068
1795
|
}
|
|
@@ -1120,6 +1847,31 @@ void test_html_markdown_attributes(void) {
|
|
|
1120
1847
|
assert_contains(html, "<div>", "HTML preserved without markdown attribute");
|
|
1121
1848
|
apex_free_string(html);
|
|
1122
1849
|
|
|
1850
|
+
/* Reference-style links in markdown="1" blocks resolve definitions from full document */
|
|
1851
|
+
const char *ref_in_block =
|
|
1852
|
+
"<blockquote class=\"tip\" markdown=\"1\">\n"
|
|
1853
|
+
"See the [extension page][ext] for details.\n"
|
|
1854
|
+
"</blockquote>\n\n"
|
|
1855
|
+
"[ext]: https://example.com/ext \"Extension page\"";
|
|
1856
|
+
html = apex_markdown_to_html(ref_in_block, strlen(ref_in_block), &opts);
|
|
1857
|
+
assert_contains(html, "<a href=\"https://example.com/ext\"", "markdown=\"1\" resolves reference-style links");
|
|
1858
|
+
assert_contains(html, "extension page</a>", "markdown=\"1\" reference link text preserved");
|
|
1859
|
+
apex_free_string(html);
|
|
1860
|
+
|
|
1861
|
+
/* Reference footnotes in markdown="1" blocks resolve definitions from full document */
|
|
1862
|
+
const char *fn_in_block =
|
|
1863
|
+
"<blockquote class=\"tip\" markdown=\"1\">\n"
|
|
1864
|
+
"See footnote[^note] here.\n"
|
|
1865
|
+
"</blockquote>\n\n"
|
|
1866
|
+
"[^note]: Footnote text.";
|
|
1867
|
+
html = apex_markdown_to_html(fn_in_block, strlen(fn_in_block), &opts);
|
|
1868
|
+
assert_contains(html, "class=\"footnote-ref\"", "markdown=\"1\" resolves reference footnotes");
|
|
1869
|
+
assert_contains(html, "href=\"#fn-note\"", "markdown=\"1\" footnote links to block anchor");
|
|
1870
|
+
assert_contains(html, "<section class=\"footnotes\"", "markdown=\"1\" footnotes section rendered in block");
|
|
1871
|
+
assert_contains(html, "Footnote text.", "markdown=\"1\" footnote definition rendered in block");
|
|
1872
|
+
assert_not_contains(html, "See footnote[^note]", "markdown=\"1\" footnote not left literal");
|
|
1873
|
+
apex_free_string(html);
|
|
1874
|
+
|
|
1123
1875
|
bool had_failures = suite_end(suite_failures);
|
|
1124
1876
|
print_suite_title("HTML Markdown Attributes Tests", had_failures, false);
|
|
1125
1877
|
}
|
|
@@ -1506,6 +2258,37 @@ void test_abbreviations(void) {
|
|
|
1506
2258
|
assert_contains(html, "<abbr title=\"abbreviation syntax\">ABBR</abbr>", "Inline abbr when mixed");
|
|
1507
2259
|
apex_free_string(html);
|
|
1508
2260
|
|
|
2261
|
+
/* Regression for issue #31: preprocessing must preserve physical lines
|
|
2262
|
+
* longer than the old 1024-byte abbreviation and 2048-byte ALD buffers. */
|
|
2263
|
+
const char *tail_marker = "END-OF-LONG-LINE";
|
|
2264
|
+
const size_t prefix_len = 2160;
|
|
2265
|
+
size_t marker_len = strlen(tail_marker);
|
|
2266
|
+
char *long_line = malloc(prefix_len + marker_len + 2);
|
|
2267
|
+
if (long_line) {
|
|
2268
|
+
memset(long_line, 'a', prefix_len);
|
|
2269
|
+
memcpy(long_line + prefix_len, tail_marker, marker_len);
|
|
2270
|
+
long_line[prefix_len + marker_len] = '\n';
|
|
2271
|
+
long_line[prefix_len + marker_len + 1] = '\0';
|
|
2272
|
+
|
|
2273
|
+
const apex_mode_t modes[] = {
|
|
2274
|
+
APEX_MODE_MULTIMARKDOWN,
|
|
2275
|
+
APEX_MODE_KRAMDOWN,
|
|
2276
|
+
APEX_MODE_UNIFIED,
|
|
2277
|
+
APEX_MODE_QUARTO
|
|
2278
|
+
};
|
|
2279
|
+
const char *mode_names[] = {"MMD", "Kramdown", "Unified", "Quarto"};
|
|
2280
|
+
for (size_t i = 0; i < sizeof(modes) / sizeof(modes[0]); i++) {
|
|
2281
|
+
apex_options long_opts = apex_options_for_mode(modes[i]);
|
|
2282
|
+
html = apex_markdown_to_html(long_line, strlen(long_line), &long_opts);
|
|
2283
|
+
test_resultf(html && strstr(html, tail_marker) != NULL,
|
|
2284
|
+
"%s mode preserves lines longer than 2048 bytes", mode_names[i]);
|
|
2285
|
+
apex_free_string(html);
|
|
2286
|
+
}
|
|
2287
|
+
free(long_line);
|
|
2288
|
+
} else {
|
|
2289
|
+
test_result(false, "Allocate issue #31 long-line regression input");
|
|
2290
|
+
}
|
|
2291
|
+
|
|
1509
2292
|
bool had_failures = suite_end(suite_failures);
|
|
1510
2293
|
print_suite_title("Abbreviations Tests", had_failures, false);
|
|
1511
2294
|
}
|
|
@@ -1746,6 +2529,26 @@ void test_special_markers(void) {
|
|
|
1746
2529
|
assert_contains(html, "Section 3", "Last section");
|
|
1747
2530
|
apex_free_string(html);
|
|
1748
2531
|
|
|
2532
|
+
/* Special markers NOT converted inside inline code span */
|
|
2533
|
+
html = apex_markdown_to_html("`<!--PAUSE:15-->`", 17, &opts);
|
|
2534
|
+
assert_contains(html, "<code><!--PAUSE:15--></code>", "Pause marker preserved in inline code");
|
|
2535
|
+
assert_not_contains(html, "data-pause", "Pause marker not rendered in inline code");
|
|
2536
|
+
apex_free_string(html);
|
|
2537
|
+
|
|
2538
|
+
/* Special markers NOT converted inside indented code block */
|
|
2539
|
+
const char *indented_break = " <!--BREAK-->";
|
|
2540
|
+
html = apex_markdown_to_html(indented_break, strlen(indented_break), &opts);
|
|
2541
|
+
assert_contains(html, "<!--BREAK-->", "Break marker preserved in indented code");
|
|
2542
|
+
assert_not_contains(html, "page-break-after", "Break marker not rendered in indented code");
|
|
2543
|
+
apex_free_string(html);
|
|
2544
|
+
|
|
2545
|
+
/* Special markers NOT converted inside fenced code block */
|
|
2546
|
+
const char *fenced = "```\n<!--BREAK-->\n```";
|
|
2547
|
+
html = apex_markdown_to_html(fenced, strlen(fenced), &opts);
|
|
2548
|
+
assert_contains(html, "<!--BREAK-->", "Break marker preserved in fenced code");
|
|
2549
|
+
assert_not_contains(html, "page-break-after", "Break marker not rendered in fenced code");
|
|
2550
|
+
apex_free_string(html);
|
|
2551
|
+
|
|
1749
2552
|
bool had_failures = suite_end(suite_failures);
|
|
1750
2553
|
print_suite_title("Special Markers Tests", had_failures, false);
|
|
1751
2554
|
}
|
|
@@ -2265,12 +3068,29 @@ void test_mixed_lists(void) {
|
|
|
2265
3068
|
html = apex_markdown_to_html(alpha_with_nested_alpha, strlen(alpha_with_nested_alpha), &unified_opts);
|
|
2266
3069
|
assert_contains(html, "<ol style=\"list-style-type: lower-alpha\">", "Alpha list keeps style with nested alpha sublist");
|
|
2267
3070
|
assert_not_contains(html, "[apex-alpha-list:", "No leaked alpha marker token after nested alpha sublist");
|
|
3071
|
+
assert_not_contains(html, "<p>Test</p>\n<ol>", "Synthetic nested alpha sublist stays tight without paragraph wrapper");
|
|
3072
|
+
apex_free_string(html);
|
|
3073
|
+
|
|
3074
|
+
/* Numeric parent list with indented alpha sublist → nested <ol style="lower-alpha"> */
|
|
3075
|
+
const char *numeric_with_nested_alpha = "1. One\n2. Two\n\ta. Two-One\n\tb. Two-Two\n3. Three\n";
|
|
3076
|
+
html = apex_markdown_to_html(numeric_with_nested_alpha, strlen(numeric_with_nested_alpha), &unified_opts);
|
|
3077
|
+
assert_contains(html, "<ol style=\"list-style-type: lower-alpha\">",
|
|
3078
|
+
"Alpha sublist under numeric list gets lower-alpha styling");
|
|
3079
|
+
assert_contains(html, "<li>Two-One</li>", "Nested alpha item text preserved");
|
|
3080
|
+
assert_not_contains(html, "<!-- apex-alpha-list-", "Alpha HTML comment marker stripped from output");
|
|
2268
3081
|
apex_free_string(html);
|
|
2269
3082
|
|
|
2270
3083
|
const char *numeric_with_nested_ordered = "1. Test\n2. Test\n\t3. Test\n\t4. Test\n5. Test\n";
|
|
2271
3084
|
html = apex_markdown_to_html(numeric_with_nested_ordered, strlen(numeric_with_nested_ordered), &unified_opts);
|
|
2272
3085
|
assert_contains(html, "<ol start=\"3\">", "Numeric nested ordered sublist renders as nested ordered list");
|
|
2273
3086
|
assert_not_contains(html, "2. Test\n 3. Test", "Nested ordered items are not flattened into parent text");
|
|
3087
|
+
assert_not_contains(html, "<p>Test</p>\n<ol start=\"3\">", "Synthetic nested ordered list stays tight without paragraph wrapper");
|
|
3088
|
+
apex_free_string(html);
|
|
3089
|
+
|
|
3090
|
+
const char *numeric_with_explicit_loose_nested_ordered = "1. Test\n2. Test\n\n\t3. Test\n\t4. Test\n5. Test\n";
|
|
3091
|
+
html = apex_markdown_to_html(numeric_with_explicit_loose_nested_ordered, strlen(numeric_with_explicit_loose_nested_ordered), &unified_opts);
|
|
3092
|
+
assert_contains(html, "<ol start=\"3\">", "Explicit blank line nested ordered sublist renders");
|
|
3093
|
+
assert_contains(html, "<p>Test</p>\n<ol start=\"3\">", "Explicit blank line keeps loose list paragraph wrapper");
|
|
2274
3094
|
apex_free_string(html);
|
|
2275
3095
|
|
|
2276
3096
|
bool had_failures = suite_end(suite_failures);
|