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
|
@@ -18,6 +18,105 @@ void test_toc(void) {
|
|
|
18
18
|
opts.enable_marked_extensions = true;
|
|
19
19
|
char *html;
|
|
20
20
|
|
|
21
|
+
/* -t toc: Markdown list, default depth 1-3 */
|
|
22
|
+
apex_options toc_opts = apex_options_default();
|
|
23
|
+
toc_opts.output_format = APEX_OUTPUT_TOC;
|
|
24
|
+
const char *doc =
|
|
25
|
+
"# Introduction\n\n"
|
|
26
|
+
"## Getting Started\n\n"
|
|
27
|
+
"### Installation\n\n"
|
|
28
|
+
"#### Too Deep\n\n"
|
|
29
|
+
"## Configuration\n\n"
|
|
30
|
+
"# API\n"
|
|
31
|
+
"{: .no_toc}\n";
|
|
32
|
+
char *md = apex_markdown_to_html(doc, strlen(doc), &toc_opts);
|
|
33
|
+
assert_contains(md, "- [Introduction](#introduction)\n", "TOC md has H1");
|
|
34
|
+
assert_contains(md, " - [Getting Started](#getting-started)\n", "TOC md indents H2");
|
|
35
|
+
assert_contains(md, " - [Installation](#installation)\n", "TOC md indents H3");
|
|
36
|
+
assert_not_contains(md, "Too Deep", "TOC md excludes H4 by default");
|
|
37
|
+
assert_not_contains(md, "API", "TOC md excludes no_toc heading");
|
|
38
|
+
assert_not_contains(md, "<nav", "TOC md is not HTML");
|
|
39
|
+
apex_free_string(md);
|
|
40
|
+
|
|
41
|
+
toc_opts.toc_min = 2;
|
|
42
|
+
toc_opts.toc_max = 4;
|
|
43
|
+
md = apex_markdown_to_html(doc, strlen(doc), &toc_opts);
|
|
44
|
+
assert_not_contains(md, "Introduction", "toc-min-max excludes H1");
|
|
45
|
+
assert_contains(md, "Too Deep", "toc-min-max includes H4");
|
|
46
|
+
apex_free_string(md);
|
|
47
|
+
|
|
48
|
+
/* id-format mmd */
|
|
49
|
+
toc_opts = apex_options_default();
|
|
50
|
+
toc_opts.output_format = APEX_OUTPUT_TOC;
|
|
51
|
+
toc_opts.id_format = 1; /* MMD */
|
|
52
|
+
md = apex_markdown_to_html("# Hello World\n", 14, &toc_opts);
|
|
53
|
+
assert_contains(md, "- [Hello World](#helloworld)\n", "TOC md respects MMD id-format");
|
|
54
|
+
apex_free_string(md);
|
|
55
|
+
|
|
56
|
+
/* manual ID */
|
|
57
|
+
toc_opts = apex_options_default();
|
|
58
|
+
toc_opts.output_format = APEX_OUTPUT_TOC;
|
|
59
|
+
md = apex_markdown_to_html("# Custom {#my-custom}\n", 22, &toc_opts);
|
|
60
|
+
assert_contains(md, "- [Custom](#my-custom)\n", "TOC md uses manual heading ID");
|
|
61
|
+
apex_free_string(md);
|
|
62
|
+
|
|
63
|
+
/* id-format kramdown */
|
|
64
|
+
toc_opts = apex_options_default();
|
|
65
|
+
toc_opts.output_format = APEX_OUTPUT_TOC;
|
|
66
|
+
toc_opts.id_format = 2; /* Kramdown */
|
|
67
|
+
md = apex_markdown_to_html("# Trailing Dash-\n", 17, &toc_opts);
|
|
68
|
+
assert_contains(md, "- [Trailing Dash-](#trailing-dash-)\n", "TOC md respects Kramdown id-format");
|
|
69
|
+
apex_free_string(md);
|
|
70
|
+
|
|
71
|
+
/* empty */
|
|
72
|
+
toc_opts = apex_options_default();
|
|
73
|
+
toc_opts.output_format = APEX_OUTPUT_TOC;
|
|
74
|
+
md = apex_markdown_to_html("No headings here.\n", 18, &toc_opts);
|
|
75
|
+
if (md && (md[0] == '\0' || strcmp(md, "\n") == 0)) {
|
|
76
|
+
test_result(true, "TOC md empty when no headings");
|
|
77
|
+
} else {
|
|
78
|
+
test_result(false, "TOC md empty when no headings");
|
|
79
|
+
}
|
|
80
|
+
apex_free_string(md);
|
|
81
|
+
|
|
82
|
+
/* Bare {{TOC}} should use option defaults (1-3): exclude H4 */
|
|
83
|
+
const char *bare_default_depth =
|
|
84
|
+
"# H1\n\n{{TOC}}\n\n## H2\n\n### H3\n\n#### H4";
|
|
85
|
+
html = apex_markdown_to_html(bare_default_depth, strlen(bare_default_depth), &opts);
|
|
86
|
+
assert_contains(html, "href=\"#h1\"", "Bare TOC includes H1 by default");
|
|
87
|
+
assert_contains(html, "href=\"#h3\"", "Bare TOC includes H3 by default");
|
|
88
|
+
assert_not_contains(html, "href=\"#h4\"", "Bare TOC excludes H4 by default (max 3)");
|
|
89
|
+
apex_free_string(html);
|
|
90
|
+
|
|
91
|
+
/* --toc-min-max via options: 2,4 */
|
|
92
|
+
apex_options depth_opts = opts;
|
|
93
|
+
depth_opts.toc_min = 2;
|
|
94
|
+
depth_opts.toc_max = 4;
|
|
95
|
+
html = apex_markdown_to_html(bare_default_depth, strlen(bare_default_depth), &depth_opts);
|
|
96
|
+
assert_not_contains(html, "href=\"#h1\"", "toc_min=2 excludes H1");
|
|
97
|
+
assert_contains(html, "href=\"#h4\"", "toc_max=4 includes H4");
|
|
98
|
+
apex_free_string(html);
|
|
99
|
+
|
|
100
|
+
/* Marker override wins over options */
|
|
101
|
+
const char *override_toc =
|
|
102
|
+
"# H1\n\n{{TOC:1-6}}\n\n## H2\n\n### H3\n\n#### H4\n\n##### H5";
|
|
103
|
+
html = apex_markdown_to_html(override_toc, strlen(override_toc), &depth_opts);
|
|
104
|
+
assert_contains(html, "href=\"#h1\"", "Marker range overrides toc_min");
|
|
105
|
+
assert_contains(html, "href=\"#h5\"", "Marker range overrides toc_max");
|
|
106
|
+
apex_free_string(html);
|
|
107
|
+
|
|
108
|
+
/* Partial marker max under custom toc_min/toc_max: unspecified min is 1, not option min */
|
|
109
|
+
apex_options partial_opts = opts;
|
|
110
|
+
partial_opts.toc_min = 2;
|
|
111
|
+
partial_opts.toc_max = 4;
|
|
112
|
+
const char *partial_max =
|
|
113
|
+
"# H1\n\n<!--TOC max5-->\n\n## H2\n\n### H3\n\n#### H4\n\n##### H5\n\n###### H6";
|
|
114
|
+
html = apex_markdown_to_html(partial_max, strlen(partial_max), &partial_opts);
|
|
115
|
+
assert_contains(html, "href=\"#h1\"", "Partial max marker uses min=1 not option toc_min");
|
|
116
|
+
assert_contains(html, "href=\"#h5\"", "Partial max marker includes H5");
|
|
117
|
+
assert_not_contains(html, "href=\"#h6\"", "Partial max marker excludes H6");
|
|
118
|
+
apex_free_string(html);
|
|
119
|
+
|
|
21
120
|
/* Test basic TOC marker */
|
|
22
121
|
const char *doc_with_toc = "# Header 1\n\n<!--TOC-->\n\n## Header 2\n\n### Header 3";
|
|
23
122
|
html = apex_markdown_to_html(doc_with_toc, strlen(doc_with_toc), &opts);
|
|
@@ -56,6 +155,36 @@ void test_toc(void) {
|
|
|
56
155
|
}
|
|
57
156
|
apex_free_string(html);
|
|
58
157
|
|
|
158
|
+
/* Regression: {{TOC:2}} must not parse beyond marker into later IDs */
|
|
159
|
+
const char *single_depth_toc =
|
|
160
|
+
"# Table of Contents Test\n\n{{TOC:2}}\n\nLorem ipsum\n\n## Section 1\n\n## Section 2\n\n## Section 3";
|
|
161
|
+
html = apex_markdown_to_html(single_depth_toc, strlen(single_depth_toc), &opts);
|
|
162
|
+
assert_contains(html, "<nav class=\"toc\">", "Single depth TOC nav rendered");
|
|
163
|
+
assert_contains(html, "href=\"#section-1\"", "Single depth TOC includes Section 1");
|
|
164
|
+
assert_contains(html, "href=\"#section-2\"", "Single depth TOC includes Section 2");
|
|
165
|
+
assert_contains(html, "href=\"#section-3\"", "Single depth TOC includes Section 3");
|
|
166
|
+
apex_free_string(html);
|
|
167
|
+
|
|
168
|
+
/* Regression: same-level headings in {{TOC:2-6}} should remain siblings */
|
|
169
|
+
const char *same_level_range_toc =
|
|
170
|
+
"# Table of Contents Test\n\n{{TOC:2-6}}\n\nLorem ipsum\n\n## Section 1\n\n## Section 2\n\n## Section 3";
|
|
171
|
+
html = apex_markdown_to_html(same_level_range_toc, strlen(same_level_range_toc), &opts);
|
|
172
|
+
assert_contains(html, "href=\"#section-1\"", "Range TOC includes Section 1");
|
|
173
|
+
assert_contains(html, "href=\"#section-2\"", "Range TOC includes Section 2");
|
|
174
|
+
assert_contains(html, "href=\"#section-3\"", "Range TOC includes Section 3");
|
|
175
|
+
assert_not_contains(html, "href=\"#section-1\">Section 1</a>\n <ul>",
|
|
176
|
+
"Range TOC does not nest same-level headings");
|
|
177
|
+
apex_free_string(html);
|
|
178
|
+
|
|
179
|
+
/* Regression: TOC labels should not include source indentation/newline whitespace */
|
|
180
|
+
const char *toc_whitespace =
|
|
181
|
+
"# Top\n\n{{TOC:2}}\n\n## Section 1\n\n##\tSection 2\n\n## Section 3";
|
|
182
|
+
html = apex_markdown_to_html(toc_whitespace, strlen(toc_whitespace), &opts);
|
|
183
|
+
assert_contains(html, "href=\"#section-1\">Section 1</a>", "TOC trims label whitespace");
|
|
184
|
+
assert_contains(html, "href=\"#section-2\">Section 2</a>", "TOC normalizes tab/newline whitespace");
|
|
185
|
+
assert_contains(html, "href=\"#section-3\">Section 3</a>", "TOC collapses internal whitespace runs");
|
|
186
|
+
apex_free_string(html);
|
|
187
|
+
|
|
59
188
|
/* Test TOC with max depth only */
|
|
60
189
|
const char *max_toc = "# H1\n\n<!--TOC max2-->\n\n## H2\n\n### H3";
|
|
61
190
|
html = apex_markdown_to_html(max_toc, strlen(max_toc), &opts);
|
|
@@ -86,6 +215,52 @@ void test_toc(void) {
|
|
|
86
215
|
assert_contains(html, "Section", "TOC includes section");
|
|
87
216
|
apex_free_string(html);
|
|
88
217
|
|
|
218
|
+
/* Escaped MMD TOC markers must not be expanded */
|
|
219
|
+
const char *escaped_toc =
|
|
220
|
+
"# Title\n\nMarked also recognizes MultiMarkdown-style \\{\\{TOC\\}\\}, "
|
|
221
|
+
"and Pandoc-style `{{TOC:2-6}}`.\n\n## Section";
|
|
222
|
+
html = apex_markdown_to_html(escaped_toc, strlen(escaped_toc), &opts);
|
|
223
|
+
assert_contains(html, "{{TOC}}", "Escaped MMD TOC marker preserved as literal text");
|
|
224
|
+
assert_not_contains(html, "<nav class=\"toc\">", "Escaped MMD TOC marker not expanded");
|
|
225
|
+
assert_contains(html, "<code>{{TOC:2-6}}</code>", "Pandoc-style TOC marker preserved in inline code");
|
|
226
|
+
apex_free_string(html);
|
|
227
|
+
|
|
228
|
+
/* Kramdown {:toc} inside indented code must not be converted */
|
|
229
|
+
apex_options kram_opts2 = apex_options_for_mode(APEX_MODE_KRAMDOWN);
|
|
230
|
+
kram_opts2.enable_marked_extensions = true;
|
|
231
|
+
const char *kramdown_toc_code = "# Title\n\n {:toc}\n\n## Section";
|
|
232
|
+
html = apex_markdown_to_html(kramdown_toc_code, strlen(kramdown_toc_code), &kram_opts2);
|
|
233
|
+
assert_contains(html, "{:toc}", "Kramdown {:toc} preserved in indented code");
|
|
234
|
+
assert_not_contains(html, "<nav class=\"toc\">", "Kramdown {:toc} not expanded in indented code");
|
|
235
|
+
apex_free_string(html);
|
|
236
|
+
|
|
237
|
+
/* Pandoc-style {{TOC:2-6}} inside fenced code must not be rendered */
|
|
238
|
+
const char *toc_range_fenced = "# Title\n\n```\n{{TOC:2-6}}\n```\n\n## Section\n\n### Sub";
|
|
239
|
+
html = apex_markdown_to_html(toc_range_fenced, strlen(toc_range_fenced), &opts);
|
|
240
|
+
assert_contains(html, "{{TOC:2-6}}", "Pandoc-style TOC marker preserved in fenced code");
|
|
241
|
+
assert_not_contains(html, "<nav class=\"toc\">", "Pandoc-style TOC marker not expanded in fenced code");
|
|
242
|
+
apex_free_string(html);
|
|
243
|
+
|
|
244
|
+
/* Pandoc-style {{TOC:2-6}} inside indented code must not be rendered */
|
|
245
|
+
const char *toc_range_indented = "# Title\n\n {{TOC:2-6}}\n\n## Section\n\n### Sub";
|
|
246
|
+
html = apex_markdown_to_html(toc_range_indented, strlen(toc_range_indented), &opts);
|
|
247
|
+
assert_contains(html, "{{TOC:2-6}}", "Pandoc-style TOC marker preserved in indented code");
|
|
248
|
+
assert_not_contains(html, "<nav class=\"toc\">", "Pandoc-style TOC marker not expanded in indented code");
|
|
249
|
+
apex_free_string(html);
|
|
250
|
+
|
|
251
|
+
/* HTML <!--TOC--> inside inline code must not be rendered */
|
|
252
|
+
html = apex_markdown_to_html("# Title\n\nUse `<!--TOC-->` here.\n\n## Section", 44, &opts);
|
|
253
|
+
assert_contains(html, "<code><!--TOC--></code>", "HTML TOC marker preserved in inline code");
|
|
254
|
+
assert_not_contains(html, "<nav class=\"toc\">", "HTML TOC marker not expanded in inline code");
|
|
255
|
+
apex_free_string(html);
|
|
256
|
+
|
|
257
|
+
/* Kramdown {:toc} inside fenced code must not be converted */
|
|
258
|
+
const char *kramdown_toc_fenced = "# Title\n\n```\n{:toc}\n```\n\n## Section";
|
|
259
|
+
html = apex_markdown_to_html(kramdown_toc_fenced, strlen(kramdown_toc_fenced), &kram_opts2);
|
|
260
|
+
assert_contains(html, "{:toc}", "Kramdown {:toc} preserved in fenced code");
|
|
261
|
+
assert_not_contains(html, "<nav class=\"toc\">", "Kramdown {:toc} not expanded in fenced code");
|
|
262
|
+
apex_free_string(html);
|
|
263
|
+
|
|
89
264
|
/* Test document without TOC marker */
|
|
90
265
|
const char *no_toc = "# Header\n\nContent";
|
|
91
266
|
html = apex_markdown_to_html(no_toc, strlen(no_toc), &opts);
|
|
@@ -165,6 +340,58 @@ void test_toc(void) {
|
|
|
165
340
|
}
|
|
166
341
|
apex_free_string(html);
|
|
167
342
|
|
|
343
|
+
/* Manual {#id} must appear in TOC hrefs */
|
|
344
|
+
const char *manual_id_toc =
|
|
345
|
+
"# Custom {#my-custom}\n\n{{TOC}}\n\n## Other";
|
|
346
|
+
html = apex_markdown_to_html(manual_id_toc, strlen(manual_id_toc), &opts);
|
|
347
|
+
assert_contains(html, "href=\"#my-custom\"", "TOC uses manual heading ID");
|
|
348
|
+
apex_free_string(html);
|
|
349
|
+
|
|
350
|
+
/* Structured TOC entries API for library / Swift consumers */
|
|
351
|
+
{
|
|
352
|
+
const char *entries_doc =
|
|
353
|
+
"# Introduction\n\n"
|
|
354
|
+
"## Getting Started\n\n"
|
|
355
|
+
"### Installation\n\n"
|
|
356
|
+
"#### Too Deep\n\n"
|
|
357
|
+
"# Skip Me\n"
|
|
358
|
+
"{: .no_toc}\n";
|
|
359
|
+
size_t count = 0;
|
|
360
|
+
apex_toc_entry *entries = apex_markdown_to_toc_entries(
|
|
361
|
+
entries_doc, strlen(entries_doc), NULL, &count);
|
|
362
|
+
assert_option_bool(count == 3, true, "toc entries default depth count is 3");
|
|
363
|
+
if (entries && count >= 3) {
|
|
364
|
+
assert_option_bool(entries[0].level == 1, true, "toc entries[0] level is 1");
|
|
365
|
+
assert_option_string(entries[0].text, "Introduction", "toc entries[0] text");
|
|
366
|
+
assert_option_string(entries[0].id, "introduction", "toc entries[0] id");
|
|
367
|
+
assert_option_bool(entries[1].level == 2, true, "toc entries[1] level is 2");
|
|
368
|
+
assert_option_string(entries[1].id, "getting-started", "toc entries[1] id");
|
|
369
|
+
assert_option_bool(entries[2].level == 3, true, "toc entries[2] level is 3");
|
|
370
|
+
assert_option_string(entries[2].id, "installation", "toc entries[2] id");
|
|
371
|
+
} else {
|
|
372
|
+
test_result(false, "toc entries array populated");
|
|
373
|
+
}
|
|
374
|
+
apex_toc_entries_free(entries, count);
|
|
375
|
+
|
|
376
|
+
apex_options depth_opts = apex_options_default();
|
|
377
|
+
depth_opts.toc_min = 2;
|
|
378
|
+
depth_opts.toc_max = 4;
|
|
379
|
+
entries = apex_markdown_to_toc_entries(entries_doc, strlen(entries_doc),
|
|
380
|
+
&depth_opts, &count);
|
|
381
|
+
assert_option_bool(count == 3, true, "toc entries toc_min/max count");
|
|
382
|
+
if (entries && count >= 1) {
|
|
383
|
+
assert_option_bool(entries[0].level == 2, true, "toc entries respects toc_min");
|
|
384
|
+
assert_option_string(entries[count - 1].text, "Too Deep",
|
|
385
|
+
"toc entries includes H4 when toc_max=4");
|
|
386
|
+
}
|
|
387
|
+
apex_toc_entries_free(entries, count);
|
|
388
|
+
|
|
389
|
+
entries = apex_markdown_to_toc_entries("No headings.\n", 12, NULL, &count);
|
|
390
|
+
assert_option_bool(count == 0, true, "toc entries empty when no headings");
|
|
391
|
+
assert_option_bool(entries == NULL, true, "toc entries NULL when empty");
|
|
392
|
+
apex_toc_entries_free(entries, count);
|
|
393
|
+
}
|
|
394
|
+
|
|
168
395
|
bool had_failures = suite_end(suite_failures);
|
|
169
396
|
print_suite_title("TOC Generation Tests", had_failures, false);
|
|
170
397
|
}
|
|
@@ -273,6 +500,13 @@ void test_pretty_html(void) {
|
|
|
273
500
|
assert_contains(html, "<strong>bold</strong>", "Inline elements not split");
|
|
274
501
|
apex_free_string(html);
|
|
275
502
|
|
|
503
|
+
/* TOC links should not get indentation whitespace inside anchor text */
|
|
504
|
+
html = apex_markdown_to_html("# Top\n\n{{TOC:2}}\n\n## Section 1", 31, &opts);
|
|
505
|
+
assert_contains(html, "<a href=\"#section-1\">Section 1</a>", "Pretty mode keeps TOC link text clean");
|
|
506
|
+
assert_not_contains(html, "<a href=\"#section-1\"> Section 1</a>",
|
|
507
|
+
"Pretty mode does not pad TOC link text");
|
|
508
|
+
apex_free_string(html);
|
|
509
|
+
|
|
276
510
|
/* Test table formatting */
|
|
277
511
|
const char *table = "| A | B |\n|---|---|\n| C | D |";
|
|
278
512
|
html = apex_markdown_to_html(table, strlen(table), &opts);
|
|
@@ -473,10 +707,118 @@ void test_terminal_output(void) {
|
|
|
473
707
|
test_result(strstr(out, "", 17, &opts);
|
|
716
|
+
test_result(out != NULL, "paginate_symbols terminal256 produces output");
|
|
717
|
+
apex_free_string(out);
|
|
718
|
+
|
|
476
719
|
bool had_failures = suite_end(suite_failures);
|
|
477
720
|
print_suite_title("Terminal Output Tests", had_failures, false);
|
|
478
721
|
}
|
|
479
722
|
|
|
723
|
+
/**
|
|
724
|
+
* Test RTF output
|
|
725
|
+
*/
|
|
726
|
+
void test_rtf_output(void) {
|
|
727
|
+
int suite_failures = suite_start();
|
|
728
|
+
print_suite_title("RTF Output Tests", false, true);
|
|
729
|
+
|
|
730
|
+
apex_options opts = apex_options_default();
|
|
731
|
+
opts.output_format = APEX_OUTPUT_RTF;
|
|
732
|
+
char *out;
|
|
733
|
+
|
|
734
|
+
const char *md =
|
|
735
|
+
"# Title\n\n"
|
|
736
|
+
"A paragraph with **bold**, *italic*, ~~strike~~, and `code`.\n\n"
|
|
737
|
+
"Visit [Example](https://example.com).\n\n"
|
|
738
|
+
"- Item one\n"
|
|
739
|
+
"- Item two\n\n"
|
|
740
|
+
"> A quote\n\n"
|
|
741
|
+
"```\ncode block\n```\n\n"
|
|
742
|
+
"| A | B |\n| --- | --- |\n| 1 | 2 |\n\n"
|
|
743
|
+
"H~2~O and E=mc^2^.\n\n"
|
|
744
|
+
"---\n";
|
|
745
|
+
|
|
746
|
+
out = apex_markdown_to_html(md, strlen(md), &opts);
|
|
747
|
+
assert_contains(out, "{\\rtf1", "RTF has document header");
|
|
748
|
+
assert_contains(out, "\\fonttbl", "RTF has font table");
|
|
749
|
+
assert_contains(out, "Title", "RTF includes heading text");
|
|
750
|
+
assert_contains(out, "\\b ", "RTF has bold");
|
|
751
|
+
assert_contains(out, "\\i ", "RTF has italic");
|
|
752
|
+
assert_contains(out, "\\strike", "RTF has strikethrough");
|
|
753
|
+
assert_contains(out, "\\f1", "RTF uses mono font for code");
|
|
754
|
+
assert_contains(out, "HYPERLINK", "RTF has hyperlink field");
|
|
755
|
+
assert_contains(out, "example.com", "RTF link URL present");
|
|
756
|
+
assert_contains(out, "\\bullet", "RTF has bullet list");
|
|
757
|
+
assert_contains(out, "\\trowd", "RTF has table row");
|
|
758
|
+
assert_contains(out, "code block", "RTF has code block text");
|
|
759
|
+
assert_contains(out, "\\super", "RTF has superscript");
|
|
760
|
+
assert_contains(out, "\\sub", "RTF has subscript");
|
|
761
|
+
test_result(out != NULL && strstr(out, "}") != NULL, "RTF document contains closing brace");
|
|
762
|
+
apex_free_string(out);
|
|
763
|
+
|
|
764
|
+
/* Ordered list alone so mixed-list merging cannot absorb it into a ul */
|
|
765
|
+
out = apex_markdown_to_html("1. First\n2. Second\n", 20, &opts);
|
|
766
|
+
assert_contains(out, "1. ", "RTF has ordered list marker");
|
|
767
|
+
assert_contains(out, "First", "RTF ordered list item text");
|
|
768
|
+
apex_free_string(out);
|
|
769
|
+
|
|
770
|
+
/* Footnotes endnotes */
|
|
771
|
+
const char *fn = "Note.[^1]\n\n[^1]: Footnote body.\n";
|
|
772
|
+
out = apex_markdown_to_html(fn, strlen(fn), &opts);
|
|
773
|
+
assert_contains(out, "{\\rtf1", "Footnote doc is RTF");
|
|
774
|
+
assert_contains(out, "Footnotes", "RTF endnotes section");
|
|
775
|
+
assert_contains(out, "Footnote body", "RTF footnote body");
|
|
776
|
+
apex_free_string(out);
|
|
777
|
+
|
|
778
|
+
/* Direct serializer */
|
|
779
|
+
out = apex_markdown_to_html("Hi", 2, &opts);
|
|
780
|
+
test_result(out != NULL && strncmp(out, "{\\rtf1", 6) == 0, "apex_markdown_to_html RTF path");
|
|
781
|
+
apex_free_string(out);
|
|
782
|
+
|
|
783
|
+
/* Remote images: alt becomes a hyperlink; no raw URL dump in body */
|
|
784
|
+
{
|
|
785
|
+
const char *remote =
|
|
786
|
+
"\n";
|
|
787
|
+
out = apex_markdown_to_html(remote, strlen(remote), &opts);
|
|
788
|
+
assert_contains(out, "HYPERLINK", "Remote image uses HYPERLINK fallback");
|
|
789
|
+
assert_contains(out, "Version: 1.1.12", "Remote image shows alt text");
|
|
790
|
+
assert_contains(out, "img.shields.io", "Remote image URL in HYPERLINK field");
|
|
791
|
+
test_result(out != NULL && strstr(out, "] (") == NULL, "Remote image does not dump ] (url)");
|
|
792
|
+
test_result(out != NULL && strstr(out, "\\pict") == NULL, "Remote image is not embedded as pict");
|
|
793
|
+
apex_free_string(out);
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
{
|
|
797
|
+
const char *empty_alt = "\n";
|
|
798
|
+
out = apex_markdown_to_html(empty_alt, strlen(empty_alt), &opts);
|
|
799
|
+
assert_contains(out, "HYPERLINK", "Empty-alt remote image is still a link");
|
|
800
|
+
assert_contains(out, "image", "Empty-alt remote image uses image label");
|
|
801
|
+
apex_free_string(out);
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
/* Local JPEG embeds without embed_images */
|
|
805
|
+
opts.embed_images = false;
|
|
806
|
+
opts.base_directory = "tests/fixtures/images";
|
|
807
|
+
{
|
|
808
|
+
const char *local = "\n";
|
|
809
|
+
out = apex_markdown_to_html(local, strlen(local), &opts);
|
|
810
|
+
assert_contains(out, "\\pict", "Local JPEG embeds as RTF pict");
|
|
811
|
+
assert_contains(out, "jpegblip", "Local JPEG uses jpegblip");
|
|
812
|
+
test_result(out != NULL && strstr(out, "HYPERLINK") == NULL,
|
|
813
|
+
"Embedded local image is not a hyperlink fallback");
|
|
814
|
+
apex_free_string(out);
|
|
815
|
+
}
|
|
816
|
+
opts.base_directory = NULL;
|
|
817
|
+
|
|
818
|
+
bool had_failures = suite_end(suite_failures);
|
|
819
|
+
print_suite_title("RTF Output Tests", had_failures, false);
|
|
820
|
+
}
|
|
821
|
+
|
|
480
822
|
/**
|
|
481
823
|
* Test header ID generation
|
|
482
824
|
*/
|
|
@@ -817,6 +1159,186 @@ void test_indices(void) {
|
|
|
817
1159
|
const char *textindex_explicit = "This uses [key combinations]{^}.";
|
|
818
1160
|
html = apex_markdown_to_html(textindex_explicit, strlen(textindex_explicit), &opts);
|
|
819
1161
|
assert_contains(html, "class=\"index\"", "TextIndex explicit term generates marker");
|
|
1162
|
+
assert_contains(html, "key combinations", "TextIndex explicit term keeps visible text");
|
|
1163
|
+
assert_not_contains(html, "[key combinations]", "TextIndex explicit term strips square brackets");
|
|
1164
|
+
assert_contains(html, "key combinations <a class=\"index-return\"", "TextIndex index lists explicit term");
|
|
1165
|
+
apex_free_string(html);
|
|
1166
|
+
|
|
1167
|
+
/* Bare {^} indexes only the preceding word; [phrase]{^} is for multi-word terms */
|
|
1168
|
+
const char *textindex_word =
|
|
1169
|
+
"Most mechanical keyboard firmware{^} supports the use of [key combinations]{^}.";
|
|
1170
|
+
html = apex_markdown_to_html(textindex_word, strlen(textindex_word), &opts);
|
|
1171
|
+
assert_contains(html, "firmware<span class=\"index\"", "TextIndex bare {^} leaves preceding word");
|
|
1172
|
+
assert_not_contains(html, "Most mechanical keyboard firmware <a", "TextIndex bare {^} indexes word only, not whole phrase");
|
|
1173
|
+
assert_contains(html, "firmware <a class=\"index-return\"", "TextIndex bare {^} index entry is firmware");
|
|
1174
|
+
assert_not_contains(html, "[key combinations]", "TextIndex phrase form strips brackets in sentence");
|
|
1175
|
+
assert_contains(html, "of key combinations<span class=\"index\"", "TextIndex phrase form keeps unbracketed text");
|
|
1176
|
+
apex_free_string(html);
|
|
1177
|
+
|
|
1178
|
+
/* Quoted heading override / standalone mark (TextIndex Fig. 2) */
|
|
1179
|
+
const char *textindex_quoted =
|
|
1180
|
+
"This is a standalone mark: {^\"foo bar\"} and this one has an overridden [heading]{^\"foo baz\"}.";
|
|
1181
|
+
html = apex_markdown_to_html(textindex_quoted, strlen(textindex_quoted), &opts);
|
|
1182
|
+
assert_not_contains(html, "{^", "TextIndex quoted marks are consumed (not left for superscript)");
|
|
1183
|
+
assert_not_contains(html, "{<sup>", "TextIndex quoted marks are not turned into superscript");
|
|
1184
|
+
assert_contains(html, "standalone mark: <span class=\"index\"", "TextIndex standalone quoted mark leaves only index span");
|
|
1185
|
+
assert_contains(html, "overridden heading<span class=\"index\"", "TextIndex override keeps visible heading without brackets");
|
|
1186
|
+
assert_not_contains(html, "[heading]", "TextIndex override strips square brackets");
|
|
1187
|
+
assert_contains(html, "foo bar <a class=\"index-return\"", "TextIndex standalone quoted mark indexes foo bar");
|
|
1188
|
+
assert_contains(html, "foo baz <a class=\"index-return\"", "TextIndex override indexes quoted term not visible heading");
|
|
1189
|
+
assert_not_contains(html, "heading <a class=\"index-return\"", "TextIndex override does not index visible heading text");
|
|
1190
|
+
apex_free_string(html);
|
|
1191
|
+
|
|
1192
|
+
/* TextIndex Fig. 4-5: underscore emphasis and * / ** wildcards */
|
|
1193
|
+
const char *textindex_emph =
|
|
1194
|
+
"This entry will be _emphasised_{^} in the index. "
|
|
1195
|
+
"This expanded entry won't be _emphasised_{^\"* (nope)\"}.";
|
|
1196
|
+
html = apex_markdown_to_html(textindex_emph, strlen(textindex_emph), &opts);
|
|
1197
|
+
assert_contains(html, "<em>emphasised</em><span class=\"index\"",
|
|
1198
|
+
"TextIndex emphasised heading keeps emphasis in body");
|
|
1199
|
+
assert_contains(html, "<em>emphasised</em> <a class=\"index-return\"",
|
|
1200
|
+
"TextIndex emphasised heading is emphasised in index");
|
|
1201
|
+
assert_not_contains(html, "_emphasised_",
|
|
1202
|
+
"TextIndex emphasised heading does not keep raw underscores in index");
|
|
1203
|
+
assert_contains(html, "emphasised (nope) <a class=\"index-return\"",
|
|
1204
|
+
"TextIndex * wildcard expands preceding without emphasis");
|
|
1205
|
+
assert_not_contains(html, "* (nope)",
|
|
1206
|
+
"TextIndex * wildcard is not left literal in index");
|
|
1207
|
+
apex_free_string(html);
|
|
1208
|
+
|
|
1209
|
+
const char *textindex_emph_strip = "This entry won't be _emphasised_{^*}.";
|
|
1210
|
+
html = apex_markdown_to_html(textindex_emph_strip, strlen(textindex_emph_strip), &opts);
|
|
1211
|
+
assert_contains(html, "emphasised <a class=\"index-return\"",
|
|
1212
|
+
"TextIndex {^*} strips emphasis via wildcard");
|
|
1213
|
+
assert_not_contains(html, "<em>emphasised</em> <a class=\"index-return\"",
|
|
1214
|
+
"TextIndex {^*} index entry is not emphasised");
|
|
1215
|
+
assert_not_contains(html, "_emphasised_",
|
|
1216
|
+
"TextIndex {^*} does not keep raw underscores in index");
|
|
1217
|
+
apex_free_string(html);
|
|
1218
|
+
|
|
1219
|
+
const char *textindex_emph_lower = "Introducing _Widget_{^\"**\"}.";
|
|
1220
|
+
html = apex_markdown_to_html(textindex_emph_lower, strlen(textindex_emph_lower), &opts);
|
|
1221
|
+
assert_contains(html, "widget <a class=\"index-return\"",
|
|
1222
|
+
"TextIndex ** wildcard lowercases stripped preceding");
|
|
1223
|
+
apex_free_string(html);
|
|
1224
|
+
|
|
1225
|
+
/* TextIndex Fig. 6: prefix wildcards — same heading merges locators */
|
|
1226
|
+
const char *textindex_prefix =
|
|
1227
|
+
"Sir Winston Leonard Spencer Churchill{^\"*, Sir Winston Leonard Spencer\"} "
|
|
1228
|
+
"was Prime Minister from 1940 to 1945.\n\n"
|
|
1229
|
+
"Churchill{^*^} was born on 30 November 1874.";
|
|
1230
|
+
html = apex_markdown_to_html(textindex_prefix, strlen(textindex_prefix), &opts);
|
|
1231
|
+
assert_not_contains(html, "Churchill^",
|
|
1232
|
+
"TextIndex *^ is not expanded as a plain * wildcard");
|
|
1233
|
+
assert_contains(html, "href=\"#idxref-0\"", "TextIndex merged entry includes first locator");
|
|
1234
|
+
assert_contains(html, "href=\"#idxref-1\"", "TextIndex merged entry includes second locator");
|
|
1235
|
+
{
|
|
1236
|
+
int heading_hits = 0;
|
|
1237
|
+
for (const char *p = html; (p = strstr(p, "Churchill, Sir Winston Leonard Spencer <a class=\"index-return\"")) != NULL; p++) {
|
|
1238
|
+
heading_hits++;
|
|
1239
|
+
}
|
|
1240
|
+
test_result(heading_hits == 1, "TextIndex *^ merges into one index entry with both locators");
|
|
1241
|
+
}
|
|
1242
|
+
apex_free_string(html);
|
|
1243
|
+
|
|
1244
|
+
/* Duplicate mmark marks also merge locators */
|
|
1245
|
+
const char *mmark_dup = "Alpha (!Alpha) here.\n\nMore about Alpha (!Alpha) there.";
|
|
1246
|
+
html = apex_markdown_to_html(mmark_dup, strlen(mmark_dup), &opts);
|
|
1247
|
+
assert_contains(html, "href=\"#idxref-0\"", "mmark merged entry has first locator");
|
|
1248
|
+
assert_contains(html, "href=\"#idxref-1\"", "mmark merged entry has second locator");
|
|
1249
|
+
{
|
|
1250
|
+
int hits = 0;
|
|
1251
|
+
for (const char *p = html; (p = strstr(p, "Alpha <a class=\"index-return\"")) != NULL; p++) {
|
|
1252
|
+
hits++;
|
|
1253
|
+
}
|
|
1254
|
+
test_result(hits == 1, "mmark duplicate marks merge into one entry");
|
|
1255
|
+
}
|
|
1256
|
+
apex_free_string(html);
|
|
1257
|
+
|
|
1258
|
+
/* TextIndex Fig. 11-12: see / see-also cross-references */
|
|
1259
|
+
const char *textindex_see =
|
|
1260
|
+
"This function can be [dangerous]{^\"destructive operation\"|safety>\"of functions\"}.";
|
|
1261
|
+
html = apex_markdown_to_html(textindex_see, strlen(textindex_see), &opts);
|
|
1262
|
+
assert_contains(html, "be dangerous", "TextIndex see-type keeps visible bracketed text");
|
|
1263
|
+
assert_not_contains(html, "[dangerous]", "TextIndex see-type strips brackets");
|
|
1264
|
+
assert_contains(html, "destructive operation", "TextIndex see-type indexes overridden heading");
|
|
1265
|
+
assert_contains(html, "<em>See</em>", "TextIndex see-type renders See in italics");
|
|
1266
|
+
assert_contains(html, "safety: of functions", "TextIndex see-type shows hierarchical path with colon");
|
|
1267
|
+
assert_not_contains(html, "href=\"#idxref-", "TextIndex see-type does not record a locator");
|
|
1268
|
+
apex_free_string(html);
|
|
1269
|
+
|
|
1270
|
+
const char *textindex_see_also =
|
|
1271
|
+
"Even typing on a keyboard can be surprisingly risky{^\"risk\"|ergonomics;+safety}.";
|
|
1272
|
+
html = apex_markdown_to_html(textindex_see_also, strlen(textindex_see_also), &opts);
|
|
1273
|
+
assert_contains(html, "<em>See</em>", "TextIndex mixed xref renders See");
|
|
1274
|
+
assert_contains(html, "ergonomics", "TextIndex mixed xref includes see target");
|
|
1275
|
+
assert_contains(html, "<em>See also</em>", "TextIndex mixed xref renders See also");
|
|
1276
|
+
assert_contains(html, "safety", "TextIndex mixed xref includes also target");
|
|
1277
|
+
assert_not_contains(html, "href=\"#idxref-", "TextIndex see-type in mark suppresses locator");
|
|
1278
|
+
apex_free_string(html);
|
|
1279
|
+
|
|
1280
|
+
const char *textindex_also_only =
|
|
1281
|
+
"A mention of risk{^\"risk\"|+safety} alone.";
|
|
1282
|
+
html = apex_markdown_to_html(textindex_also_only, strlen(textindex_also_only), &opts);
|
|
1283
|
+
assert_contains(html, "<em>See also</em>", "TextIndex also-only renders See also");
|
|
1284
|
+
assert_contains(html, "href=\"#idxref-", "TextIndex also-only keeps locator");
|
|
1285
|
+
assert_not_contains(html, "<em>See</em> safety", "TextIndex also-only is not a see-type");
|
|
1286
|
+
apex_free_string(html);
|
|
1287
|
+
|
|
1288
|
+
/* TextIndex Fig. 13: aliases */
|
|
1289
|
+
const char *textindex_alias =
|
|
1290
|
+
"The various [operating systems]{^\"Apple (company)\">\"OS platforms\"#apple} "
|
|
1291
|
+
"on Apple devices share a common heritage.\n\n"
|
|
1292
|
+
"The majority of Apple devices are iPhones{^\"iPhone\"|+#apple}, by a large margin.";
|
|
1293
|
+
html = apex_markdown_to_html(textindex_alias, strlen(textindex_alias), &opts);
|
|
1294
|
+
assert_contains(html, "operating systems<span class=\"index\"",
|
|
1295
|
+
"TextIndex alias definition keeps visible text");
|
|
1296
|
+
assert_contains(html, "Apple (company)", "TextIndex alias definition indexes parent heading");
|
|
1297
|
+
assert_contains(html, "OS platforms", "TextIndex alias definition indexes nested heading");
|
|
1298
|
+
assert_contains(html, "iPhone", "TextIndex alias use indexes iPhone");
|
|
1299
|
+
assert_contains(html, "<em>See also</em>", "TextIndex alias use expands as see-also");
|
|
1300
|
+
assert_contains(html, "Apple (company): OS platforms",
|
|
1301
|
+
"TextIndex #apple expands to full path in see-also");
|
|
1302
|
+
assert_not_contains(html, "#apple", "TextIndex alias name is not left literal");
|
|
1303
|
+
apex_free_string(html);
|
|
1304
|
+
|
|
1305
|
+
/* TextIndex Fig. 14: unreferenced aliases */
|
|
1306
|
+
const char *textindex_unref_alias =
|
|
1307
|
+
"{^\"indeterminacy principle (Heisenberg, Werner Karl)\"##q-uncert} "
|
|
1308
|
+
"A key component of the Enterprise's transporter system is the "
|
|
1309
|
+
"Heisenberg compensator{^|#q-uncert}.";
|
|
1310
|
+
html = apex_markdown_to_html(textindex_unref_alias, strlen(textindex_unref_alias), &opts);
|
|
1311
|
+
assert_contains(html, "compensator", "TextIndex unref alias use keeps preceding word");
|
|
1312
|
+
assert_contains(html, "<em>See</em>", "TextIndex unref alias used as see-reference");
|
|
1313
|
+
assert_contains(html, "indeterminacy principle (Heisenberg, Werner Karl)",
|
|
1314
|
+
"TextIndex ## alias expands in see target");
|
|
1315
|
+
assert_not_contains(html, "#q-uncert", "TextIndex ## alias name is not left literal");
|
|
1316
|
+
assert_not_contains(html, "indeterminacy principle (Heisenberg, Werner Karl) <a class=\"index-return\"",
|
|
1317
|
+
"TextIndex ## alias does not create a locator for the definition");
|
|
1318
|
+
apex_free_string(html);
|
|
1319
|
+
|
|
1320
|
+
/* TextIndex Fig. 16: disable/enable processing with {^-} / {^+} */
|
|
1321
|
+
const char *textindex_toggle =
|
|
1322
|
+
"{^-} This index mark won't be processed: foo{^} {^+} after{^}.";
|
|
1323
|
+
html = apex_markdown_to_html(textindex_toggle, strlen(textindex_toggle), &opts);
|
|
1324
|
+
assert_contains(html, "foo{^}", "TextIndex {^-} leaves marks untouched while disabled");
|
|
1325
|
+
assert_contains(html, "after<span class=\"index\"", "TextIndex {^+} re-enables processing");
|
|
1326
|
+
assert_contains(html, "after <a class=\"index-return\"", "TextIndex mark after {^+} is indexed");
|
|
1327
|
+
assert_not_contains(html, "foo <a class=\"index-return\"", "TextIndex mark inside {^-} region is not indexed");
|
|
1328
|
+
assert_not_contains(html, "{^-}", "TextIndex {^-} toggle mark is consumed when it disables");
|
|
1329
|
+
assert_not_contains(html, "{^+}", "TextIndex {^+} toggle mark is consumed when it enables");
|
|
1330
|
+
assert_not_contains(html, "> - <a class=\"index-return\"", "TextIndex {^-} is not indexed as a heading");
|
|
1331
|
+
assert_not_contains(html, "> + <a class=\"index-return\"", "TextIndex {^+} is not indexed as a heading");
|
|
1332
|
+
apex_free_string(html);
|
|
1333
|
+
|
|
1334
|
+
const char *textindex_toggle_redundant =
|
|
1335
|
+
"{^-} hidden{^} {^-} still{^} {^+} shown{^} {^+} trailing.";
|
|
1336
|
+
html = apex_markdown_to_html(textindex_toggle_redundant, strlen(textindex_toggle_redundant), &opts);
|
|
1337
|
+
assert_contains(html, "hidden{^}", "TextIndex redundant region keeps first disabled mark literal");
|
|
1338
|
+
assert_contains(html, "{^-}", "TextIndex redundant {^-} stays when already disabled");
|
|
1339
|
+
assert_contains(html, "still{^}", "TextIndex marks stay literal until {^+}");
|
|
1340
|
+
assert_contains(html, "shown<span class=\"index\"", "TextIndex processing resumes after effective {^+}");
|
|
1341
|
+
assert_contains(html, "{^+}", "TextIndex redundant {^+} stays when already enabled");
|
|
820
1342
|
apex_free_string(html);
|
|
821
1343
|
|
|
822
1344
|
/* Test Leanpub index syntax */
|
|
@@ -963,6 +1485,36 @@ void test_indices(void) {
|
|
|
963
1485
|
assert_contains(html, "href=\"#idxref-", "Index entries link to anchors");
|
|
964
1486
|
apex_free_string(html);
|
|
965
1487
|
|
|
1488
|
+
/* Concordance files: auto-mark terms from TSV before TextIndex processing */
|
|
1489
|
+
{
|
|
1490
|
+
apex_options opts_conc = apex_options_default();
|
|
1491
|
+
opts_conc.mode = APEX_MODE_UNIFIED;
|
|
1492
|
+
opts_conc.enable_indices = true;
|
|
1493
|
+
opts_conc.enable_textindex_syntax = true;
|
|
1494
|
+
opts_conc.base_directory = "tests";
|
|
1495
|
+
const char *conc_files[] = {"test_concordance.tsv", NULL};
|
|
1496
|
+
opts_conc.concordance_files = (char **)conc_files;
|
|
1497
|
+
|
|
1498
|
+
const char *conc_doc =
|
|
1499
|
+
"A widget and another Widget appear. The MacBook is nice. Configuration helps.";
|
|
1500
|
+
html = apex_markdown_to_html(conc_doc, strlen(conc_doc), &opts_conc);
|
|
1501
|
+
assert_contains(html, "gadget <a class=\"index-return\"",
|
|
1502
|
+
"Concordance maps widget to gadget heading");
|
|
1503
|
+
assert_contains(html, "Apple laptop <a class=\"index-return\"",
|
|
1504
|
+
"Concordance case-sensitive MacBook uses quoted heading");
|
|
1505
|
+
assert_contains(html, "See", "Concordance see-ref from column 2 is rendered");
|
|
1506
|
+
assert_contains(html, "firmware", "Concordance see target firmware appears");
|
|
1507
|
+
/* Existing marks must not be rematched */
|
|
1508
|
+
const char *conc_existing = "Already marked widget{^custom}.";
|
|
1509
|
+
apex_free_string(html);
|
|
1510
|
+
html = apex_markdown_to_html(conc_existing, strlen(conc_existing), &opts_conc);
|
|
1511
|
+
assert_contains(html, "custom <a class=\"index-return\"",
|
|
1512
|
+
"Concordance skips text already inside an index mark");
|
|
1513
|
+
assert_not_contains(html, "gadget <a class=\"index-return\"",
|
|
1514
|
+
"Concordance does not rematch marked widget as gadget");
|
|
1515
|
+
apex_free_string(html);
|
|
1516
|
+
}
|
|
1517
|
+
|
|
966
1518
|
bool had_failures = suite_end(suite_failures);
|
|
967
1519
|
print_suite_title("Index Tests", had_failures, false);
|
|
968
1520
|
}
|
|
@@ -1054,6 +1606,20 @@ void test_citations(void) {
|
|
|
1054
1606
|
assert_contains(html, "mailto:", "Email autolinking still works");
|
|
1055
1607
|
apex_free_string(html);
|
|
1056
1608
|
|
|
1609
|
+
/* Test markdown mailto links with @ and query params are not re-autolinked */
|
|
1610
|
+
const char *mailto_markdown =
|
|
1611
|
+
"If you have purchased a permanent unlock or lifetime license through the Mac App Store, please "
|
|
1612
|
+
"[email the developer](mailto:marked@brettterpstra.com?subject=Marked%20License%20Crossgrade&body=Please%20include%20your%20UUID%20%28Help-%3ECopy%20UUID%20in%20Marked%29%20in%20this%20email%20for%20receipt%20verification.) "
|
|
1613
|
+
"to request a free lifetime Paddle license.";
|
|
1614
|
+
html = apex_markdown_to_html(mailto_markdown, strlen(mailto_markdown), &opts_autolink);
|
|
1615
|
+
assert_contains(
|
|
1616
|
+
html,
|
|
1617
|
+
"<a href=\"mailto:marked@brettterpstra.com?subject=Marked%20License%20Crossgrade&body=Please%20include%20your%20UUID%20%28Help-%3ECopy%20UUID%20in%20Marked%29%20in%20this%20email%20for%20receipt%20verification.\">email the developer</a>",
|
|
1618
|
+
"Markdown mailto link with query params renders as a single clean anchor");
|
|
1619
|
+
assert_not_contains(html, "[email the", "Markdown link syntax is not leaked into output");
|
|
1620
|
+
assert_not_contains(html, "mailto:developer](", "No nested autolink corruption in mailto link");
|
|
1621
|
+
apex_free_string(html);
|
|
1622
|
+
|
|
1057
1623
|
/* Test that autolink does not run inside indented code blocks */
|
|
1058
1624
|
const char *indented_code =
|
|
1059
1625
|
" x-marked://extract?url=https://example.com\n";
|
|
@@ -1309,7 +1875,7 @@ void test_combine_gitbook_like(void) {
|
|
|
1309
1875
|
}
|
|
1310
1876
|
|
|
1311
1877
|
/* Process intro with includes (none here, just sanity) */
|
|
1312
|
-
char *intro_md = apex_process_includes(intro_src, base_dir, NULL, 0);
|
|
1878
|
+
char *intro_md = apex_process_includes(intro_src, base_dir, NULL, 0, NULL, NULL, NULL);
|
|
1313
1879
|
char *intro_html = apex_markdown_to_html(intro_md ? intro_md : intro_src,
|
|
1314
1880
|
strlen(intro_md ? intro_md : intro_src),
|
|
1315
1881
|
&opts);
|
|
@@ -1342,7 +1908,7 @@ void test_combine_gitbook_like(void) {
|
|
|
1342
1908
|
return;
|
|
1343
1909
|
}
|
|
1344
1910
|
|
|
1345
|
-
char *chapter_md = apex_process_includes(chapter_src, base_dir, NULL, 0);
|
|
1911
|
+
char *chapter_md = apex_process_includes(chapter_src, base_dir, NULL, 0, NULL, NULL, NULL);
|
|
1346
1912
|
const char *chapter_final = chapter_md ? chapter_md : chapter_src;
|
|
1347
1913
|
|
|
1348
1914
|
/* Combined Markdown should contain both Chapter 1 and Section 1.1 headings */
|