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
|
@@ -25,6 +25,8 @@ static int apex_idx_size_to_int(size_t v) {
|
|
|
25
25
|
/* Index placeholder prefix - we'll use a unique marker */
|
|
26
26
|
#define INDEX_PLACEHOLDER_PREFIX "<!--IDX:"
|
|
27
27
|
#define INDEX_PLACEHOLDER_SUFFIX "-->"
|
|
28
|
+
/* Protects literal {^...} marks in {^-} regions from later superscript */
|
|
29
|
+
#define TEXTINDEX_CARET_PLACEHOLDER "APEXTICARET"
|
|
28
30
|
|
|
29
31
|
/**
|
|
30
32
|
* Check if character is valid in index term
|
|
@@ -54,6 +56,700 @@ static char *trim_string(char *str) {
|
|
|
54
56
|
return str;
|
|
55
57
|
}
|
|
56
58
|
|
|
59
|
+
/**
|
|
60
|
+
* Strip Markdown single-underscore emphasis markers, keeping the inner text.
|
|
61
|
+
* TextIndex uses _…_ in headings; wildcards insert the stripped form.
|
|
62
|
+
*/
|
|
63
|
+
static char *strip_underscore_emphasis(const char *s) {
|
|
64
|
+
if (!s) return NULL;
|
|
65
|
+
|
|
66
|
+
size_t len = strlen(s);
|
|
67
|
+
char *out = malloc(len + 1);
|
|
68
|
+
if (!out) return NULL;
|
|
69
|
+
|
|
70
|
+
size_t wi = 0;
|
|
71
|
+
for (size_t i = 0; i < len; i++) {
|
|
72
|
+
if (s[i] == '_') {
|
|
73
|
+
/* Look for a matching closing _ */
|
|
74
|
+
size_t j = i + 1;
|
|
75
|
+
while (j < len && s[j] != '_') j++;
|
|
76
|
+
if (j < len && j > i + 1) {
|
|
77
|
+
/* Copy inner text without the underscores */
|
|
78
|
+
for (size_t k = i + 1; k < j; k++) {
|
|
79
|
+
out[wi++] = s[k];
|
|
80
|
+
}
|
|
81
|
+
i = j;
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
out[wi++] = s[i];
|
|
86
|
+
}
|
|
87
|
+
out[wi] = '\0';
|
|
88
|
+
return out;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Convert TextIndex Markdown underscore emphasis to HTML <em> tags.
|
|
93
|
+
*/
|
|
94
|
+
static char *underscore_emphasis_to_html(const char *s) {
|
|
95
|
+
if (!s) return NULL;
|
|
96
|
+
|
|
97
|
+
size_t len = strlen(s);
|
|
98
|
+
/* Worst case: every char becomes part of <em></em> expansion */
|
|
99
|
+
char *out = malloc(len * 6 + 1);
|
|
100
|
+
if (!out) return NULL;
|
|
101
|
+
|
|
102
|
+
size_t wi = 0;
|
|
103
|
+
for (size_t i = 0; i < len; i++) {
|
|
104
|
+
if (s[i] == '_') {
|
|
105
|
+
size_t j = i + 1;
|
|
106
|
+
while (j < len && s[j] != '_') j++;
|
|
107
|
+
if (j < len && j > i + 1) {
|
|
108
|
+
memcpy(out + wi, "<em>", 4);
|
|
109
|
+
wi += 4;
|
|
110
|
+
for (size_t k = i + 1; k < j; k++) {
|
|
111
|
+
out[wi++] = s[k];
|
|
112
|
+
}
|
|
113
|
+
memcpy(out + wi, "</em>", 5);
|
|
114
|
+
wi += 5;
|
|
115
|
+
i = j;
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
out[wi++] = s[i];
|
|
120
|
+
}
|
|
121
|
+
out[wi] = '\0';
|
|
122
|
+
return out;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Expand TextIndex * / ** wildcards using the preceding heading.
|
|
127
|
+
* * → preceding with emphasis stripped
|
|
128
|
+
* ** → same, lowercased
|
|
129
|
+
* Returns a new string; template is unchanged.
|
|
130
|
+
*/
|
|
131
|
+
static char *expand_textindex_wildcards(const char *template, const char *preceding) {
|
|
132
|
+
if (!template) return NULL;
|
|
133
|
+
|
|
134
|
+
char *stripped = preceding ? strip_underscore_emphasis(preceding) : strdup("");
|
|
135
|
+
if (!stripped) return NULL;
|
|
136
|
+
|
|
137
|
+
char *lowered = strdup(stripped);
|
|
138
|
+
if (!lowered) {
|
|
139
|
+
free(stripped);
|
|
140
|
+
return NULL;
|
|
141
|
+
}
|
|
142
|
+
for (char *p = lowered; *p; p++) {
|
|
143
|
+
*p = (char)tolower((unsigned char)*p);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
size_t tlen = strlen(template);
|
|
147
|
+
size_t slen = strlen(stripped);
|
|
148
|
+
size_t llen = strlen(lowered);
|
|
149
|
+
/* Generous: every char could expand to lowered preceding */
|
|
150
|
+
size_t cap = tlen * (llen > slen ? llen : slen) + tlen + 1;
|
|
151
|
+
if (cap < tlen + 1) cap = tlen + 1;
|
|
152
|
+
char *out = malloc(cap);
|
|
153
|
+
if (!out) {
|
|
154
|
+
free(stripped);
|
|
155
|
+
free(lowered);
|
|
156
|
+
return NULL;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
size_t wi = 0;
|
|
160
|
+
for (size_t i = 0; i < tlen; i++) {
|
|
161
|
+
if (template[i] == '*') {
|
|
162
|
+
const char *ins = stripped;
|
|
163
|
+
size_t ilen = slen;
|
|
164
|
+
if (i + 1 < tlen && template[i + 1] == '*') {
|
|
165
|
+
ins = lowered;
|
|
166
|
+
ilen = llen;
|
|
167
|
+
i++; /* consume second * */
|
|
168
|
+
}
|
|
169
|
+
if (wi + ilen + 1 > cap) {
|
|
170
|
+
cap = (wi + ilen + 1) * 2;
|
|
171
|
+
char *n = realloc(out, cap);
|
|
172
|
+
if (!n) {
|
|
173
|
+
free(out);
|
|
174
|
+
free(stripped);
|
|
175
|
+
free(lowered);
|
|
176
|
+
return NULL;
|
|
177
|
+
}
|
|
178
|
+
out = n;
|
|
179
|
+
}
|
|
180
|
+
memcpy(out + wi, ins, ilen);
|
|
181
|
+
wi += ilen;
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
if (wi + 2 > cap) {
|
|
185
|
+
cap = (wi + 2) * 2;
|
|
186
|
+
char *n = realloc(out, cap);
|
|
187
|
+
if (!n) {
|
|
188
|
+
free(out);
|
|
189
|
+
free(stripped);
|
|
190
|
+
free(lowered);
|
|
191
|
+
return NULL;
|
|
192
|
+
}
|
|
193
|
+
out = n;
|
|
194
|
+
}
|
|
195
|
+
out[wi++] = template[i];
|
|
196
|
+
}
|
|
197
|
+
out[wi] = '\0';
|
|
198
|
+
free(stripped);
|
|
199
|
+
free(lowered);
|
|
200
|
+
return out;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* True if template uses a TextIndex * / ** wildcard.
|
|
205
|
+
*/
|
|
206
|
+
static bool textindex_has_wildcard(const char *s) {
|
|
207
|
+
return s && strchr(s, '*') != NULL;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Strip HTML tags for sorting / letter grouping of index terms.
|
|
212
|
+
*/
|
|
213
|
+
static char *strip_html_tags(const char *s) {
|
|
214
|
+
if (!s) return NULL;
|
|
215
|
+
size_t len = strlen(s);
|
|
216
|
+
char *out = malloc(len + 1);
|
|
217
|
+
if (!out) return NULL;
|
|
218
|
+
size_t wi = 0;
|
|
219
|
+
for (size_t i = 0; i < len; i++) {
|
|
220
|
+
if (s[i] == '<') {
|
|
221
|
+
while (i < len && s[i] != '>') i++;
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
out[wi++] = s[i];
|
|
225
|
+
}
|
|
226
|
+
out[wi] = '\0';
|
|
227
|
+
return out;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Detect TextIndex prefix wildcards: *^ (full path) or *^- (label only).
|
|
232
|
+
* Returns 1 for *^, 2 for *^-, 0 otherwise. Ignores trailing ! or /.
|
|
233
|
+
*/
|
|
234
|
+
static int textindex_prefix_wildcard_kind(const char *s) {
|
|
235
|
+
if (!s || s[0] != '*') return 0;
|
|
236
|
+
char buf[16];
|
|
237
|
+
size_t n = 0;
|
|
238
|
+
for (const char *p = s; *p && n + 1 < sizeof(buf); p++) {
|
|
239
|
+
if (*p == '!' || *p == '/') break;
|
|
240
|
+
buf[n++] = *p;
|
|
241
|
+
}
|
|
242
|
+
buf[n] = '\0';
|
|
243
|
+
while (n > 0 && isspace((unsigned char)buf[n - 1])) buf[--n] = '\0';
|
|
244
|
+
if (strcmp(buf, "*^-") == 0) return 2;
|
|
245
|
+
if (strcmp(buf, "*^") == 0) return 1;
|
|
246
|
+
return 0;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Case-sensitive prefix match of label against an index heading (HTML stripped).
|
|
251
|
+
*/
|
|
252
|
+
static bool textindex_heading_has_prefix(const char *heading, const char *label) {
|
|
253
|
+
if (!heading || !label || !label[0]) return false;
|
|
254
|
+
char *plain = strip_html_tags(heading);
|
|
255
|
+
if (!plain) return false;
|
|
256
|
+
size_t llen = strlen(label);
|
|
257
|
+
bool match = strncmp(plain, label, llen) == 0;
|
|
258
|
+
free(plain);
|
|
259
|
+
return match;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Find the earliest registry entry whose item or subitem starts with label.
|
|
264
|
+
* label should already have underscore emphasis stripped.
|
|
265
|
+
*/
|
|
266
|
+
static const apex_index_entry *textindex_find_prefix_entry(const apex_index_registry *registry,
|
|
267
|
+
const char *label) {
|
|
268
|
+
if (!registry || !label || !label[0]) return NULL;
|
|
269
|
+
|
|
270
|
+
const apex_index_entry *best = NULL;
|
|
271
|
+
for (const apex_index_entry *e = registry->entries; e; e = e->next) {
|
|
272
|
+
bool match = textindex_heading_has_prefix(e->item, label);
|
|
273
|
+
if (!match && e->subitem) {
|
|
274
|
+
match = textindex_heading_has_prefix(e->subitem, label);
|
|
275
|
+
}
|
|
276
|
+
if (match && (!best || e->position < best->position)) {
|
|
277
|
+
best = e;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
return best;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Parse one TextIndex path segment (quoted or bare) into newly allocated text.
|
|
285
|
+
* Advances *pp past the segment. Returns NULL on empty/failure.
|
|
286
|
+
*/
|
|
287
|
+
static char *textindex_parse_path_segment(const char **pp) {
|
|
288
|
+
const char *p = *pp;
|
|
289
|
+
while (*p && isspace((unsigned char)*p)) p++;
|
|
290
|
+
if (!*p || *p == '>' || *p == ';' || *p == '|' || *p == '~' || *p == '[') {
|
|
291
|
+
*pp = p;
|
|
292
|
+
return NULL;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
char *seg = NULL;
|
|
296
|
+
if (*p == '"' || *p == '\'') {
|
|
297
|
+
char quote = *p++;
|
|
298
|
+
const char *start = p;
|
|
299
|
+
while (*p) {
|
|
300
|
+
if (*p == '\\' && p[1]) {
|
|
301
|
+
p += 2;
|
|
302
|
+
continue;
|
|
303
|
+
}
|
|
304
|
+
if (*p == quote) break;
|
|
305
|
+
p++;
|
|
306
|
+
}
|
|
307
|
+
size_t len = (size_t)(p - start);
|
|
308
|
+
seg = malloc(len + 1);
|
|
309
|
+
if (seg) {
|
|
310
|
+
size_t wi = 0;
|
|
311
|
+
for (size_t i = 0; i < len; i++) {
|
|
312
|
+
if (start[i] == '\\' && i + 1 < len) {
|
|
313
|
+
seg[wi++] = start[++i];
|
|
314
|
+
} else {
|
|
315
|
+
seg[wi++] = start[i];
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
seg[wi] = '\0';
|
|
319
|
+
}
|
|
320
|
+
if (*p == quote) p++;
|
|
321
|
+
} else {
|
|
322
|
+
const char *start = p;
|
|
323
|
+
while (*p && *p != '>' && *p != ';' && *p != '|' && *p != '~' &&
|
|
324
|
+
*p != '[' && !isspace((unsigned char)*p)) {
|
|
325
|
+
p++;
|
|
326
|
+
}
|
|
327
|
+
size_t len = (size_t)(p - start);
|
|
328
|
+
if (len == 0) {
|
|
329
|
+
*pp = p;
|
|
330
|
+
return NULL;
|
|
331
|
+
}
|
|
332
|
+
seg = malloc(len + 1);
|
|
333
|
+
if (seg) {
|
|
334
|
+
memcpy(seg, start, len);
|
|
335
|
+
seg[len] = '\0';
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
while (*p && isspace((unsigned char)*p)) p++;
|
|
340
|
+
*pp = p;
|
|
341
|
+
return seg;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Convert a TextIndex heading path (a>b>"c d") to display form "a: b: c d"
|
|
346
|
+
* with underscore emphasis converted to <em>.
|
|
347
|
+
*/
|
|
348
|
+
static char *textindex_path_to_display(const char *path) {
|
|
349
|
+
if (!path || !path[0]) return NULL;
|
|
350
|
+
|
|
351
|
+
const char *p = path;
|
|
352
|
+
char *out = NULL;
|
|
353
|
+
size_t out_len = 0;
|
|
354
|
+
|
|
355
|
+
while (*p) {
|
|
356
|
+
while (*p && isspace((unsigned char)*p)) p++;
|
|
357
|
+
if (!*p) break;
|
|
358
|
+
if (*p == '>') {
|
|
359
|
+
p++;
|
|
360
|
+
continue;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
char *seg = textindex_parse_path_segment(&p);
|
|
364
|
+
if (!seg) break;
|
|
365
|
+
|
|
366
|
+
char *html_seg = underscore_emphasis_to_html(seg);
|
|
367
|
+
free(seg);
|
|
368
|
+
if (!html_seg) {
|
|
369
|
+
free(out);
|
|
370
|
+
return NULL;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
size_t add = strlen(html_seg);
|
|
374
|
+
size_t need = out_len + add + (out ? 2 : 0) + 1;
|
|
375
|
+
char *n = realloc(out, need);
|
|
376
|
+
if (!n) {
|
|
377
|
+
free(html_seg);
|
|
378
|
+
free(out);
|
|
379
|
+
return NULL;
|
|
380
|
+
}
|
|
381
|
+
out = n;
|
|
382
|
+
if (out_len > 0) {
|
|
383
|
+
out[out_len++] = ':';
|
|
384
|
+
out[out_len++] = ' ';
|
|
385
|
+
}
|
|
386
|
+
memcpy(out + out_len, html_seg, add);
|
|
387
|
+
out_len += add;
|
|
388
|
+
out[out_len] = '\0';
|
|
389
|
+
free(html_seg);
|
|
390
|
+
|
|
391
|
+
while (*p && isspace((unsigned char)*p)) p++;
|
|
392
|
+
if (*p == '>') p++;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
return out;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Free a cross-reference list.
|
|
400
|
+
*/
|
|
401
|
+
static void apex_index_xref_free_all(apex_index_xref *xref) {
|
|
402
|
+
while (xref) {
|
|
403
|
+
apex_index_xref *next = xref->next;
|
|
404
|
+
free(xref->target);
|
|
405
|
+
free(xref);
|
|
406
|
+
xref = next;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
static char *textindex_expand_alias_in_path(const apex_index_registry *registry, const char *path);
|
|
411
|
+
static apex_index_alias *textindex_find_alias(const apex_index_registry *registry, const char *name);
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* Parse TextIndex cross-references after '|': ergonomics;+safety;@foo;@+bar;#alias
|
|
415
|
+
* Sets *has_conventional_see if any non-inbound see-type is present.
|
|
416
|
+
* Expands #alias targets when registry is provided.
|
|
417
|
+
*/
|
|
418
|
+
static apex_index_xref *textindex_parse_xrefs(const char *spec, bool *has_conventional_see,
|
|
419
|
+
const apex_index_registry *registry) {
|
|
420
|
+
if (has_conventional_see) *has_conventional_see = false;
|
|
421
|
+
if (!spec) return NULL;
|
|
422
|
+
|
|
423
|
+
apex_index_xref *head = NULL;
|
|
424
|
+
apex_index_xref *tail = NULL;
|
|
425
|
+
const char *p = spec;
|
|
426
|
+
|
|
427
|
+
while (*p) {
|
|
428
|
+
while (*p && (isspace((unsigned char)*p) || *p == ';')) p++;
|
|
429
|
+
if (!*p) break;
|
|
430
|
+
|
|
431
|
+
bool inbound = false;
|
|
432
|
+
bool also = false;
|
|
433
|
+
if (*p == '@') {
|
|
434
|
+
inbound = true;
|
|
435
|
+
p++;
|
|
436
|
+
}
|
|
437
|
+
if (*p == '+') {
|
|
438
|
+
also = true;
|
|
439
|
+
p++;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
const char *path_start = p;
|
|
443
|
+
while (*p && *p != ';') p++;
|
|
444
|
+
size_t path_len = (size_t)(p - path_start);
|
|
445
|
+
while (path_len > 0 && isspace((unsigned char)path_start[path_len - 1])) path_len--;
|
|
446
|
+
|
|
447
|
+
if (path_len == 0) continue;
|
|
448
|
+
|
|
449
|
+
char *path_buf = malloc(path_len + 1);
|
|
450
|
+
if (!path_buf) continue;
|
|
451
|
+
memcpy(path_buf, path_start, path_len);
|
|
452
|
+
path_buf[path_len] = '\0';
|
|
453
|
+
|
|
454
|
+
char *expanded = textindex_expand_alias_in_path(registry, path_buf);
|
|
455
|
+
free(path_buf);
|
|
456
|
+
if (!expanded) continue;
|
|
457
|
+
|
|
458
|
+
char *display = NULL;
|
|
459
|
+
apex_index_alias *a = NULL;
|
|
460
|
+
if (expanded[0] == '#' ) {
|
|
461
|
+
/* Unexpanded alias — leave literal (will show in tests as failure) */
|
|
462
|
+
display = textindex_path_to_display(expanded);
|
|
463
|
+
} else {
|
|
464
|
+
/* If expansion produced a raw path, prefer alias display when exact match */
|
|
465
|
+
display = textindex_path_to_display(expanded);
|
|
466
|
+
/* Prefer stored display for pure alias expansion */
|
|
467
|
+
if (registry) {
|
|
468
|
+
for (a = registry->aliases; a; a = a->next) {
|
|
469
|
+
if (a->path && strcmp(a->path, expanded) == 0 && a->display) {
|
|
470
|
+
free(display);
|
|
471
|
+
display = strdup(a->display);
|
|
472
|
+
break;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
free(expanded);
|
|
478
|
+
if (!display || !display[0]) {
|
|
479
|
+
free(display);
|
|
480
|
+
continue;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
apex_index_xref *xref = calloc(1, sizeof(apex_index_xref));
|
|
484
|
+
if (!xref) {
|
|
485
|
+
free(display);
|
|
486
|
+
continue;
|
|
487
|
+
}
|
|
488
|
+
xref->target = display;
|
|
489
|
+
xref->also = also;
|
|
490
|
+
xref->inbound = inbound;
|
|
491
|
+
xref->next = NULL;
|
|
492
|
+
|
|
493
|
+
if (!also && !inbound && has_conventional_see) {
|
|
494
|
+
*has_conventional_see = true;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
if (!head) head = xref;
|
|
498
|
+
else tail->next = xref;
|
|
499
|
+
tail = xref;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
return head;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
/**
|
|
506
|
+
* True if character is valid in a TextIndex alias name.
|
|
507
|
+
*/
|
|
508
|
+
static bool textindex_alias_name_char(unsigned char c) {
|
|
509
|
+
return isalnum(c) || c == '-' || c == '_';
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
/**
|
|
513
|
+
* Lookup an alias by exact name.
|
|
514
|
+
*/
|
|
515
|
+
static apex_index_alias *textindex_find_alias(const apex_index_registry *registry, const char *name) {
|
|
516
|
+
if (!registry || !name) return NULL;
|
|
517
|
+
for (apex_index_alias *a = registry->aliases; a; a = a->next) {
|
|
518
|
+
if (a->name && strcmp(a->name, name) == 0) return a;
|
|
519
|
+
}
|
|
520
|
+
return NULL;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
/**
|
|
524
|
+
* Define or replace a TextIndex alias.
|
|
525
|
+
*/
|
|
526
|
+
static void textindex_define_alias(apex_index_registry *registry,
|
|
527
|
+
const char *name,
|
|
528
|
+
const char *path,
|
|
529
|
+
const char *item,
|
|
530
|
+
const char *subitem) {
|
|
531
|
+
if (!registry || !name || !name[0] || !item) return;
|
|
532
|
+
|
|
533
|
+
apex_index_alias *existing = textindex_find_alias(registry, name);
|
|
534
|
+
if (existing) {
|
|
535
|
+
free(existing->path);
|
|
536
|
+
free(existing->item);
|
|
537
|
+
free(existing->subitem);
|
|
538
|
+
free(existing->display);
|
|
539
|
+
existing->path = path ? strdup(path) : NULL;
|
|
540
|
+
existing->item = strdup(item);
|
|
541
|
+
existing->subitem = subitem ? strdup(subitem) : NULL;
|
|
542
|
+
} else {
|
|
543
|
+
existing = calloc(1, sizeof(apex_index_alias));
|
|
544
|
+
if (!existing) return;
|
|
545
|
+
existing->name = strdup(name);
|
|
546
|
+
existing->path = path ? strdup(path) : NULL;
|
|
547
|
+
existing->item = strdup(item);
|
|
548
|
+
existing->subitem = subitem ? strdup(subitem) : NULL;
|
|
549
|
+
existing->next = registry->aliases;
|
|
550
|
+
registry->aliases = existing;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
if (existing->subitem && existing->subitem[0]) {
|
|
554
|
+
size_t n = strlen(existing->item) + strlen(existing->subitem) + 3;
|
|
555
|
+
existing->display = malloc(n);
|
|
556
|
+
if (existing->display) {
|
|
557
|
+
snprintf(existing->display, n, "%s: %s", existing->item, existing->subitem);
|
|
558
|
+
}
|
|
559
|
+
} else {
|
|
560
|
+
existing->display = strdup(existing->item);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
static void textindex_free_aliases(apex_index_alias *alias) {
|
|
565
|
+
while (alias) {
|
|
566
|
+
apex_index_alias *next = alias->next;
|
|
567
|
+
free(alias->name);
|
|
568
|
+
free(alias->path);
|
|
569
|
+
free(alias->item);
|
|
570
|
+
free(alias->subitem);
|
|
571
|
+
free(alias->display);
|
|
572
|
+
free(alias);
|
|
573
|
+
alias = next;
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
/**
|
|
578
|
+
* Expand a leading #alias in a path string. Returns newly allocated expanded
|
|
579
|
+
* path (raw, with > separators) or strdup of input if no expansion.
|
|
580
|
+
*/
|
|
581
|
+
static char *textindex_expand_alias_in_path(const apex_index_registry *registry, const char *path) {
|
|
582
|
+
if (!path) return NULL;
|
|
583
|
+
if (path[0] != '#') return strdup(path);
|
|
584
|
+
|
|
585
|
+
const char *name = path + 1;
|
|
586
|
+
size_t nlen = 0;
|
|
587
|
+
while (name[nlen] && textindex_alias_name_char((unsigned char)name[nlen])) nlen++;
|
|
588
|
+
if (nlen == 0) return strdup(path);
|
|
589
|
+
|
|
590
|
+
char *name_buf = malloc(nlen + 1);
|
|
591
|
+
if (!name_buf) return strdup(path);
|
|
592
|
+
memcpy(name_buf, name, nlen);
|
|
593
|
+
name_buf[nlen] = '\0';
|
|
594
|
+
|
|
595
|
+
apex_index_alias *a = textindex_find_alias(registry, name_buf);
|
|
596
|
+
free(name_buf);
|
|
597
|
+
if (!a) return strdup(path);
|
|
598
|
+
|
|
599
|
+
/* Remaining path after #alias (e.g. #apple>extra) */
|
|
600
|
+
const char *rest = path + 1 + nlen;
|
|
601
|
+
while (*rest && isspace((unsigned char)*rest)) rest++;
|
|
602
|
+
|
|
603
|
+
if (a->path && a->path[0]) {
|
|
604
|
+
if (*rest == '>') {
|
|
605
|
+
size_t n = strlen(a->path) + strlen(rest) + 1;
|
|
606
|
+
char *out = malloc(n);
|
|
607
|
+
if (!out) return strdup(a->path);
|
|
608
|
+
snprintf(out, n, "%s%s", a->path, rest);
|
|
609
|
+
return out;
|
|
610
|
+
}
|
|
611
|
+
if (*rest) {
|
|
612
|
+
/* Alias followed by other text — treat alias as first segment */
|
|
613
|
+
size_t n = strlen(a->path) + strlen(rest) + 2;
|
|
614
|
+
char *out = malloc(n);
|
|
615
|
+
if (!out) return strdup(a->path);
|
|
616
|
+
snprintf(out, n, "%s>%s", a->path, rest);
|
|
617
|
+
return out;
|
|
618
|
+
}
|
|
619
|
+
return strdup(a->path);
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
/* Fall back to item/subitem */
|
|
623
|
+
if (a->subitem && a->subitem[0]) {
|
|
624
|
+
if (*rest == '>' || *rest) {
|
|
625
|
+
size_t n = strlen(a->item) + strlen(a->subitem) + strlen(rest) + 4;
|
|
626
|
+
char *out = malloc(n);
|
|
627
|
+
if (!out) return NULL;
|
|
628
|
+
if (*rest == '>') {
|
|
629
|
+
snprintf(out, n, "%s>%s%s", a->item, a->subitem, rest);
|
|
630
|
+
} else if (*rest) {
|
|
631
|
+
snprintf(out, n, "%s>%s>%s", a->item, a->subitem, rest);
|
|
632
|
+
} else {
|
|
633
|
+
snprintf(out, n, "%s>%s", a->item, a->subitem);
|
|
634
|
+
}
|
|
635
|
+
return out;
|
|
636
|
+
}
|
|
637
|
+
size_t n = strlen(a->item) + strlen(a->subitem) + 2;
|
|
638
|
+
char *out = malloc(n);
|
|
639
|
+
if (!out) return NULL;
|
|
640
|
+
snprintf(out, n, "%s>%s", a->item, a->subitem);
|
|
641
|
+
return out;
|
|
642
|
+
}
|
|
643
|
+
return strdup(a->item);
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
/**
|
|
647
|
+
* Strip a trailing #name or ##name from heading_part (modified in place).
|
|
648
|
+
* Sets *alias_name and *unref. Returns true if an alias definition was found.
|
|
649
|
+
*/
|
|
650
|
+
static bool textindex_strip_trailing_alias(char *heading_part, char **alias_name, bool *unref) {
|
|
651
|
+
if (alias_name) *alias_name = NULL;
|
|
652
|
+
if (unref) *unref = false;
|
|
653
|
+
if (!heading_part) return false;
|
|
654
|
+
|
|
655
|
+
/* Walk backwards outside of quotes to find a # alias definition */
|
|
656
|
+
size_t len = strlen(heading_part);
|
|
657
|
+
bool in_quote = false;
|
|
658
|
+
char quote = 0;
|
|
659
|
+
int hash_at = -1;
|
|
660
|
+
for (size_t i = 0; i < len; i++) {
|
|
661
|
+
if (in_quote) {
|
|
662
|
+
if (heading_part[i] == '\\' && i + 1 < len) {
|
|
663
|
+
i++;
|
|
664
|
+
continue;
|
|
665
|
+
}
|
|
666
|
+
if (heading_part[i] == quote) in_quote = false;
|
|
667
|
+
continue;
|
|
668
|
+
}
|
|
669
|
+
if (heading_part[i] == '"' || heading_part[i] == '\'') {
|
|
670
|
+
in_quote = true;
|
|
671
|
+
quote = heading_part[i];
|
|
672
|
+
continue;
|
|
673
|
+
}
|
|
674
|
+
if (heading_part[i] == '#') {
|
|
675
|
+
hash_at = (int)i;
|
|
676
|
+
/* ##alias — keep hash_at at the first '#', skip the second */
|
|
677
|
+
if (i + 1 < len && heading_part[i + 1] == '#') {
|
|
678
|
+
i++;
|
|
679
|
+
}
|
|
680
|
+
continue;
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
if (hash_at < 0) return false;
|
|
684
|
+
|
|
685
|
+
const char *p = heading_part + hash_at;
|
|
686
|
+
bool is_unref = false;
|
|
687
|
+
if (p[0] == '#' && p[1] == '#') {
|
|
688
|
+
is_unref = true;
|
|
689
|
+
p += 2;
|
|
690
|
+
} else {
|
|
691
|
+
p += 1;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
if (!*p || !textindex_alias_name_char((unsigned char)*p)) return false;
|
|
695
|
+
const char *name_start = p;
|
|
696
|
+
while (*p && textindex_alias_name_char((unsigned char)*p)) p++;
|
|
697
|
+
/* Only trailing alias: rest must be whitespace */
|
|
698
|
+
const char *rest = p;
|
|
699
|
+
while (*rest && isspace((unsigned char)*rest)) rest++;
|
|
700
|
+
if (*rest) return false;
|
|
701
|
+
|
|
702
|
+
size_t nlen = (size_t)(p - name_start);
|
|
703
|
+
char *name = malloc(nlen + 1);
|
|
704
|
+
if (!name) return false;
|
|
705
|
+
memcpy(name, name_start, nlen);
|
|
706
|
+
name[nlen] = '\0';
|
|
707
|
+
|
|
708
|
+
/* Truncate heading_part before the # */
|
|
709
|
+
size_t cut = (size_t)hash_at;
|
|
710
|
+
while (cut > 0 && isspace((unsigned char)heading_part[cut - 1])) cut--;
|
|
711
|
+
heading_part[cut] = '\0';
|
|
712
|
+
|
|
713
|
+
if (alias_name) *alias_name = name;
|
|
714
|
+
else free(name);
|
|
715
|
+
if (unref) *unref = is_unref;
|
|
716
|
+
return true;
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
/**
|
|
720
|
+
* Split a raw TextIndex path into item + optional subitem (HTML-converted).
|
|
721
|
+
* For A>B>C: item=A, subitem="B: C".
|
|
722
|
+
*/
|
|
723
|
+
static void textindex_path_to_item_subitem(const char *path, char **item_out, char **subitem_out) {
|
|
724
|
+
if (item_out) *item_out = NULL;
|
|
725
|
+
if (subitem_out) *subitem_out = NULL;
|
|
726
|
+
if (!path || !path[0]) return;
|
|
727
|
+
|
|
728
|
+
const char *p = path;
|
|
729
|
+
char *first = textindex_parse_path_segment(&p);
|
|
730
|
+
if (!first) return;
|
|
731
|
+
|
|
732
|
+
char *item_html = underscore_emphasis_to_html(first);
|
|
733
|
+
free(first);
|
|
734
|
+
if (!item_html) return;
|
|
735
|
+
|
|
736
|
+
while (*p && isspace((unsigned char)*p)) p++;
|
|
737
|
+
if (*p == '>') p++;
|
|
738
|
+
while (*p && isspace((unsigned char)*p)) p++;
|
|
739
|
+
|
|
740
|
+
if (!*p) {
|
|
741
|
+
if (item_out) *item_out = item_html;
|
|
742
|
+
else free(item_html);
|
|
743
|
+
return;
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
char *rest_display = textindex_path_to_display(p);
|
|
747
|
+
if (item_out) *item_out = item_html;
|
|
748
|
+
else free(item_html);
|
|
749
|
+
if (subitem_out) *subitem_out = rest_display;
|
|
750
|
+
else free(rest_display);
|
|
751
|
+
}
|
|
752
|
+
|
|
57
753
|
/**
|
|
58
754
|
* Parse mmark index syntax: (!item), (!item, subitem), (!!item, subitem)
|
|
59
755
|
* Returns length consumed, or 0 if not a match
|
|
@@ -149,14 +845,23 @@ static int parse_mmark_index(const char *text, int pos, int len,
|
|
|
149
845
|
}
|
|
150
846
|
|
|
151
847
|
/**
|
|
152
|
-
* Parse TextIndex syntax: {^}, [term]{^}, {^params}
|
|
153
|
-
*
|
|
848
|
+
* Parse TextIndex syntax: {^}, [term]{^}, {^params}, {^"quoted"}, [vis]{^"override"}
|
|
849
|
+
* Supports _underscore_ emphasis in headings and * / ** wildcards (TextIndex Fig. 4-5).
|
|
850
|
+
* Returns length consumed from {^...}, or 0 if not a match.
|
|
154
851
|
*
|
|
155
|
-
*
|
|
156
|
-
*
|
|
852
|
+
* When the term is an explicit [term] before {^}, *bracketed_prefix_out is set to
|
|
853
|
+
* the length of that "[term]" span (including brackets) so the caller can strip
|
|
854
|
+
* the brackets from already-copied output. Otherwise *bracketed_prefix_out is 0.
|
|
855
|
+
*
|
|
856
|
+
* Bare word{^} indexes only the immediate preceding word (not a multi-word phrase);
|
|
857
|
+
* use [phrase]{^} for multi-word terms. A quoted heading inside the braces
|
|
858
|
+
* ({^"foo bar"} or [visible]{^"index term"}) overrides the index entry text.
|
|
157
859
|
*/
|
|
158
860
|
static int parse_textindex(const char *text, int pos, int len,
|
|
159
|
-
apex_index_entry **entry_out
|
|
861
|
+
apex_index_entry **entry_out,
|
|
862
|
+
int *bracketed_prefix_out,
|
|
863
|
+
apex_index_registry *registry) {
|
|
864
|
+
if (bracketed_prefix_out) *bracketed_prefix_out = 0;
|
|
160
865
|
if (pos + 2 >= len) return 0;
|
|
161
866
|
|
|
162
867
|
const char *p = text + pos;
|
|
@@ -188,10 +893,56 @@ static int parse_textindex(const char *text, int pos, int len,
|
|
|
188
893
|
p++; /* Skip } */
|
|
189
894
|
int consumed = apex_idx_ptrdiff_to_int(p - (text + pos));
|
|
190
895
|
|
|
191
|
-
/*
|
|
192
|
-
char *
|
|
896
|
+
/* Split params into heading path and optional |xrefs (pipe outside quotes) */
|
|
897
|
+
char *heading_part = NULL;
|
|
898
|
+
const char *xref_spec = NULL;
|
|
899
|
+
if (params && params[0]) {
|
|
900
|
+
const char *pipe = NULL;
|
|
901
|
+
bool in_q = false;
|
|
902
|
+
char qch = 0;
|
|
903
|
+
for (const char *s = params; *s; s++) {
|
|
904
|
+
if (in_q) {
|
|
905
|
+
if (*s == '\\' && s[1]) {
|
|
906
|
+
s++;
|
|
907
|
+
continue;
|
|
908
|
+
}
|
|
909
|
+
if (*s == qch) in_q = false;
|
|
910
|
+
continue;
|
|
911
|
+
}
|
|
912
|
+
if (*s == '"' || *s == '\'') {
|
|
913
|
+
in_q = true;
|
|
914
|
+
qch = *s;
|
|
915
|
+
continue;
|
|
916
|
+
}
|
|
917
|
+
if (*s == '|') {
|
|
918
|
+
pipe = s;
|
|
919
|
+
break;
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
if (pipe) {
|
|
923
|
+
size_t hlen = (size_t)(pipe - params);
|
|
924
|
+
heading_part = malloc(hlen + 1);
|
|
925
|
+
if (heading_part) {
|
|
926
|
+
memcpy(heading_part, params, hlen);
|
|
927
|
+
heading_part[hlen] = '\0';
|
|
928
|
+
trim_string(heading_part);
|
|
929
|
+
}
|
|
930
|
+
xref_spec = pipe + 1;
|
|
931
|
+
} else {
|
|
932
|
+
heading_part = strdup(params);
|
|
933
|
+
if (heading_part) {
|
|
934
|
+
size_t rlen = strlen(heading_part);
|
|
935
|
+
while (rlen > 0 && (heading_part[rlen - 1] == '!' || heading_part[rlen - 1] == '/')) {
|
|
936
|
+
heading_part[--rlen] = '\0';
|
|
937
|
+
}
|
|
938
|
+
trim_string(heading_part);
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
/* Check for explicit visible term before {^: [term]{^...} */
|
|
944
|
+
char *visible_term = NULL;
|
|
193
945
|
if (brace_start > text && brace_start[-1] == ']') {
|
|
194
|
-
/* Look backwards for [ */
|
|
195
946
|
const char *bracket_start = brace_start - 1;
|
|
196
947
|
int lookback = 0;
|
|
197
948
|
while (bracket_start > text && *bracket_start != '[' && lookback < 200) {
|
|
@@ -200,33 +951,31 @@ static int parse_textindex(const char *text, int pos, int len,
|
|
|
200
951
|
}
|
|
201
952
|
|
|
202
953
|
if (*bracket_start == '[') {
|
|
203
|
-
/* Term is content between [ and ], excluding the brackets */
|
|
204
954
|
size_t term_len = (brace_start - 1) - (bracket_start + 1);
|
|
205
955
|
if (term_len > 0 && term_len < 200) {
|
|
206
|
-
|
|
207
|
-
if (
|
|
208
|
-
memcpy(
|
|
209
|
-
|
|
210
|
-
trim_string(
|
|
956
|
+
visible_term = malloc(term_len + 1);
|
|
957
|
+
if (visible_term) {
|
|
958
|
+
memcpy(visible_term, bracket_start + 1, term_len);
|
|
959
|
+
visible_term[term_len] = '\0';
|
|
960
|
+
trim_string(visible_term);
|
|
961
|
+
}
|
|
962
|
+
if (bracketed_prefix_out) {
|
|
963
|
+
*bracketed_prefix_out = apex_idx_ptrdiff_to_int(brace_start - bracket_start);
|
|
211
964
|
}
|
|
212
965
|
}
|
|
213
966
|
}
|
|
214
967
|
}
|
|
215
968
|
|
|
216
|
-
/*
|
|
217
|
-
|
|
969
|
+
/* Implicit preceding word when immediately adjacent (no whitespace). */
|
|
970
|
+
char *implicit_word = NULL;
|
|
971
|
+
if ((!visible_term || !visible_term[0]) &&
|
|
972
|
+
brace_start > text && !isspace((unsigned char)brace_start[-1])) {
|
|
218
973
|
const char *word_end = brace_start;
|
|
219
|
-
/* Skip backwards over whitespace */
|
|
220
|
-
while (word_end > text && isspace((unsigned char)word_end[-1])) {
|
|
221
|
-
word_end--;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
/* Extract word/phrase (up to 50 chars backwards) */
|
|
225
974
|
const char *word_start = word_end;
|
|
226
975
|
int word_chars = 0;
|
|
227
976
|
while (word_start > text && word_chars < 50) {
|
|
228
|
-
char c = word_start[-1];
|
|
229
|
-
if (isalnum(c) || c == '
|
|
977
|
+
unsigned char c = (unsigned char)word_start[-1];
|
|
978
|
+
if (isalnum(c) || c == '-' || c == '_') {
|
|
230
979
|
word_start--;
|
|
231
980
|
word_chars++;
|
|
232
981
|
} else {
|
|
@@ -236,51 +985,165 @@ static int parse_textindex(const char *text, int pos, int len,
|
|
|
236
985
|
|
|
237
986
|
if (word_chars > 0) {
|
|
238
987
|
size_t term_len = word_end - word_start;
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
term[term_len] = '\0';
|
|
245
|
-
trim_string(term);
|
|
246
|
-
}
|
|
988
|
+
implicit_word = malloc(term_len + 1);
|
|
989
|
+
if (implicit_word) {
|
|
990
|
+
memcpy(implicit_word, word_start, term_len);
|
|
991
|
+
implicit_word[term_len] = '\0';
|
|
992
|
+
trim_string(implicit_word);
|
|
247
993
|
}
|
|
248
994
|
}
|
|
249
995
|
}
|
|
250
996
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
997
|
+
char *preceding = NULL;
|
|
998
|
+
if (visible_term && visible_term[0]) {
|
|
999
|
+
preceding = visible_term;
|
|
1000
|
+
visible_term = NULL;
|
|
1001
|
+
} else if (implicit_word && implicit_word[0]) {
|
|
1002
|
+
preceding = implicit_word;
|
|
1003
|
+
implicit_word = NULL;
|
|
1004
|
+
}
|
|
1005
|
+
free(visible_term);
|
|
1006
|
+
free(implicit_word);
|
|
1007
|
+
|
|
1008
|
+
/* Trailing #alias / ##alias on the heading path */
|
|
1009
|
+
char *alias_def_name = NULL;
|
|
1010
|
+
bool unref_alias = false;
|
|
1011
|
+
if (heading_part) {
|
|
1012
|
+
textindex_strip_trailing_alias(heading_part, &alias_def_name, &unref_alias);
|
|
254
1013
|
}
|
|
255
1014
|
|
|
256
|
-
|
|
257
|
-
char *
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
1015
|
+
char *path_raw = NULL;
|
|
1016
|
+
char *item_html = NULL;
|
|
1017
|
+
char *subitem_html = NULL;
|
|
1018
|
+
bool heading_already_html = false;
|
|
1019
|
+
|
|
1020
|
+
if (heading_part && heading_part[0]) {
|
|
1021
|
+
/* Expand leading #alias, then handle wildcards / paths */
|
|
1022
|
+
char *expanded = textindex_expand_alias_in_path(registry, heading_part);
|
|
1023
|
+
free(heading_part);
|
|
1024
|
+
heading_part = NULL;
|
|
1025
|
+
path_raw = expanded;
|
|
1026
|
+
|
|
1027
|
+
/* Bare prefix / simple wildcards without hierarchy */
|
|
1028
|
+
int pkind = textindex_prefix_wildcard_kind(path_raw);
|
|
1029
|
+
if (pkind) {
|
|
1030
|
+
char *label = preceding ? strip_underscore_emphasis(preceding) : NULL;
|
|
1031
|
+
const apex_index_entry *match =
|
|
1032
|
+
(label && registry) ? textindex_find_prefix_entry(registry, label) : NULL;
|
|
1033
|
+
free(label);
|
|
1034
|
+
if (!match) {
|
|
1035
|
+
free(path_raw);
|
|
1036
|
+
free(alias_def_name);
|
|
1037
|
+
free(preceding);
|
|
1038
|
+
free(params);
|
|
1039
|
+
if (bracketed_prefix_out) *bracketed_prefix_out = 0;
|
|
1040
|
+
return 0;
|
|
1041
|
+
}
|
|
1042
|
+
if (pkind == 2) {
|
|
1043
|
+
char *lab2 = strip_underscore_emphasis(preceding);
|
|
1044
|
+
if (match->subitem && textindex_heading_has_prefix(match->subitem, lab2)) {
|
|
1045
|
+
item_html = strdup(match->subitem);
|
|
1046
|
+
} else {
|
|
1047
|
+
item_html = strdup(match->item);
|
|
1048
|
+
}
|
|
1049
|
+
free(lab2);
|
|
1050
|
+
} else {
|
|
1051
|
+
item_html = strdup(match->item);
|
|
1052
|
+
if (match->subitem) subitem_html = strdup(match->subitem);
|
|
268
1053
|
}
|
|
1054
|
+
heading_already_html = true;
|
|
1055
|
+
free(path_raw);
|
|
1056
|
+
path_raw = NULL;
|
|
1057
|
+
} else if (textindex_has_wildcard(path_raw) && !strchr(path_raw, '>')) {
|
|
1058
|
+
/* Single-segment wildcard heading: strip quotes if present */
|
|
1059
|
+
char *tmpl = path_raw;
|
|
1060
|
+
char *owned = path_raw;
|
|
1061
|
+
if ((tmpl[0] == '"' || tmpl[0] == '\'') && strlen(tmpl) >= 2) {
|
|
1062
|
+
char quote = tmpl[0];
|
|
1063
|
+
size_t tlen = strlen(tmpl);
|
|
1064
|
+
if (tmpl[tlen - 1] == quote) {
|
|
1065
|
+
tmpl[tlen - 1] = '\0';
|
|
1066
|
+
tmpl++;
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
if (!preceding) {
|
|
1070
|
+
free(owned);
|
|
1071
|
+
free(alias_def_name);
|
|
1072
|
+
free(preceding);
|
|
1073
|
+
free(params);
|
|
1074
|
+
if (bracketed_prefix_out) *bracketed_prefix_out = 0;
|
|
1075
|
+
return 0;
|
|
1076
|
+
}
|
|
1077
|
+
char *expanded_w = expand_textindex_wildcards(tmpl, preceding);
|
|
1078
|
+
free(owned);
|
|
1079
|
+
path_raw = expanded_w;
|
|
1080
|
+
/* Expanded wildcard text is a single heading, not a > path */
|
|
1081
|
+
if (path_raw) {
|
|
1082
|
+
item_html = underscore_emphasis_to_html(path_raw);
|
|
1083
|
+
}
|
|
1084
|
+
} else {
|
|
1085
|
+
textindex_path_to_item_subitem(path_raw, &item_html, &subitem_html);
|
|
269
1086
|
}
|
|
1087
|
+
} else {
|
|
1088
|
+
free(heading_part);
|
|
1089
|
+
heading_part = NULL;
|
|
270
1090
|
}
|
|
271
1091
|
|
|
272
|
-
|
|
273
|
-
|
|
1092
|
+
if (!item_html && preceding) {
|
|
1093
|
+
item_html = underscore_emphasis_to_html(preceding);
|
|
1094
|
+
if (!path_raw && preceding) path_raw = strdup(preceding);
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
/* Define alias for this path when #name / ##name was present */
|
|
1098
|
+
if (alias_def_name && item_html) {
|
|
1099
|
+
textindex_define_alias(registry, alias_def_name, path_raw, item_html, subitem_html);
|
|
1100
|
+
}
|
|
1101
|
+
free(alias_def_name);
|
|
1102
|
+
free(preceding);
|
|
1103
|
+
|
|
1104
|
+
/* ##alias: define only — consume mark, no index entry */
|
|
1105
|
+
if (unref_alias) {
|
|
1106
|
+
free(item_html);
|
|
1107
|
+
free(subitem_html);
|
|
1108
|
+
free(path_raw);
|
|
1109
|
+
free(params);
|
|
1110
|
+
*entry_out = NULL;
|
|
1111
|
+
return consumed;
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
if (!item_html || !item_html[0]) {
|
|
1115
|
+
free(item_html);
|
|
1116
|
+
free(subitem_html);
|
|
1117
|
+
free(path_raw);
|
|
1118
|
+
free(params);
|
|
1119
|
+
if (bracketed_prefix_out) *bracketed_prefix_out = 0;
|
|
1120
|
+
return 0;
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
/* Cross-references after '|' (with #alias expansion) */
|
|
1124
|
+
apex_index_xref *xrefs = NULL;
|
|
1125
|
+
bool has_conventional_see = false;
|
|
1126
|
+
if (xref_spec) {
|
|
1127
|
+
xrefs = textindex_parse_xrefs(xref_spec, &has_conventional_see, registry);
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
(void)heading_already_html; /* item_html already final */
|
|
1131
|
+
|
|
1132
|
+
apex_index_entry *entry = apex_index_entry_new(item_html, APEX_INDEX_TEXTINDEX);
|
|
1133
|
+
free(item_html);
|
|
1134
|
+
free(path_raw);
|
|
274
1135
|
if (entry) {
|
|
275
|
-
entry->subitem =
|
|
1136
|
+
entry->subitem = subitem_html;
|
|
1137
|
+
entry->xrefs = xrefs;
|
|
1138
|
+
entry->suppress_locator = has_conventional_see;
|
|
276
1139
|
*entry_out = entry;
|
|
277
1140
|
} else {
|
|
278
|
-
free(
|
|
279
|
-
|
|
1141
|
+
free(subitem_html);
|
|
1142
|
+
apex_index_xref_free_all(xrefs);
|
|
1143
|
+
if (bracketed_prefix_out) *bracketed_prefix_out = 0;
|
|
280
1144
|
}
|
|
281
1145
|
|
|
282
1146
|
free(params);
|
|
283
|
-
|
|
284
1147
|
return consumed;
|
|
285
1148
|
}
|
|
286
1149
|
|
|
@@ -429,9 +1292,11 @@ apex_index_entry *apex_index_entry_new(const char *item, apex_index_syntax_t syn
|
|
|
429
1292
|
entry->item = strdup(item);
|
|
430
1293
|
entry->subitem = NULL;
|
|
431
1294
|
entry->primary = false;
|
|
1295
|
+
entry->suppress_locator = false;
|
|
432
1296
|
entry->position = 0;
|
|
433
1297
|
entry->anchor_id = NULL;
|
|
434
1298
|
entry->syntax_type = syntax_type;
|
|
1299
|
+
entry->xrefs = NULL;
|
|
435
1300
|
entry->next = NULL;
|
|
436
1301
|
|
|
437
1302
|
return entry;
|
|
@@ -446,6 +1311,7 @@ void apex_index_entry_free(apex_index_entry *entry) {
|
|
|
446
1311
|
free(entry->item);
|
|
447
1312
|
free(entry->subitem);
|
|
448
1313
|
free(entry->anchor_id);
|
|
1314
|
+
apex_index_xref_free_all(entry->xrefs);
|
|
449
1315
|
free(entry);
|
|
450
1316
|
}
|
|
451
1317
|
|
|
@@ -463,6 +1329,8 @@ void apex_free_index_registry(apex_index_registry *registry) {
|
|
|
463
1329
|
}
|
|
464
1330
|
|
|
465
1331
|
registry->entries = NULL;
|
|
1332
|
+
textindex_free_aliases(registry->aliases);
|
|
1333
|
+
registry->aliases = NULL;
|
|
466
1334
|
registry->count = 0;
|
|
467
1335
|
registry->next_ref_id = 0;
|
|
468
1336
|
}
|
|
@@ -530,10 +1398,84 @@ char *apex_process_index_entries(const char *text, apex_index_registry *registry
|
|
|
530
1398
|
const char *read = text;
|
|
531
1399
|
char *write = output;
|
|
532
1400
|
size_t remaining = capacity;
|
|
1401
|
+
bool textindex_processing = true; /* {^-} / {^+} toggles */
|
|
533
1402
|
|
|
534
1403
|
while (*read) {
|
|
535
1404
|
apex_index_entry *entry = NULL;
|
|
536
1405
|
int consumed = 0;
|
|
1406
|
+
int bracketed_prefix = 0;
|
|
1407
|
+
|
|
1408
|
+
/* TextIndex processing toggles: {^-} disables, {^+} enables.
|
|
1409
|
+
* Effective toggles are removed; redundant ones are left untouched. */
|
|
1410
|
+
if (options->enable_textindex_syntax &&
|
|
1411
|
+
read[0] == '{' && read[1] == '^' &&
|
|
1412
|
+
(read[2] == '-' || read[2] == '+') && read[3] == '}') {
|
|
1413
|
+
if (read[2] == '-' && textindex_processing) {
|
|
1414
|
+
textindex_processing = false;
|
|
1415
|
+
read += 4;
|
|
1416
|
+
continue;
|
|
1417
|
+
}
|
|
1418
|
+
if (read[2] == '+' && !textindex_processing) {
|
|
1419
|
+
textindex_processing = true;
|
|
1420
|
+
read += 4;
|
|
1421
|
+
continue;
|
|
1422
|
+
}
|
|
1423
|
+
/* Redundant toggle: emit with caret placeholder (survives superscript) */
|
|
1424
|
+
const char *lit = (read[2] == '-') ? "{APEXTICARET-}" : "{APEXTICARET+}";
|
|
1425
|
+
size_t lit_len = strlen(lit);
|
|
1426
|
+
if (remaining < lit_len + 1) {
|
|
1427
|
+
size_t used = write - output;
|
|
1428
|
+
capacity = (used + lit_len + 1) * 2;
|
|
1429
|
+
char *new_output = realloc(output, capacity);
|
|
1430
|
+
if (!new_output) {
|
|
1431
|
+
free(output);
|
|
1432
|
+
return NULL;
|
|
1433
|
+
}
|
|
1434
|
+
output = new_output;
|
|
1435
|
+
write = output + used;
|
|
1436
|
+
remaining = capacity - used;
|
|
1437
|
+
}
|
|
1438
|
+
memcpy(write, lit, lit_len);
|
|
1439
|
+
write += lit_len;
|
|
1440
|
+
remaining -= lit_len;
|
|
1441
|
+
read += 4;
|
|
1442
|
+
continue;
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1445
|
+
/* While disabled, leave TextIndex marks literal (protect ^ from superscript) */
|
|
1446
|
+
if (options->enable_textindex_syntax && !textindex_processing &&
|
|
1447
|
+
read[0] == '{' && read[1] == '^') {
|
|
1448
|
+
const char *end = read + 2;
|
|
1449
|
+
while (*end && *end != '}' && *end != '\n') end++;
|
|
1450
|
+
if (*end == '}') {
|
|
1451
|
+
size_t inner_len = (size_t)(end - (read + 2));
|
|
1452
|
+
size_t ph_len = strlen(TEXTINDEX_CARET_PLACEHOLDER);
|
|
1453
|
+
size_t lit_len = 1 + ph_len + inner_len + 1; /* { PLACEHOLDER inner } */
|
|
1454
|
+
if (remaining < lit_len + 1) {
|
|
1455
|
+
size_t used = write - output;
|
|
1456
|
+
capacity = (used + lit_len + 1) * 2;
|
|
1457
|
+
char *new_output = realloc(output, capacity);
|
|
1458
|
+
if (!new_output) {
|
|
1459
|
+
free(output);
|
|
1460
|
+
return NULL;
|
|
1461
|
+
}
|
|
1462
|
+
output = new_output;
|
|
1463
|
+
write = output + used;
|
|
1464
|
+
remaining = capacity - used;
|
|
1465
|
+
}
|
|
1466
|
+
*write++ = '{';
|
|
1467
|
+
memcpy(write, TEXTINDEX_CARET_PLACEHOLDER, ph_len);
|
|
1468
|
+
write += ph_len;
|
|
1469
|
+
if (inner_len > 0) {
|
|
1470
|
+
memcpy(write, read + 2, inner_len);
|
|
1471
|
+
write += inner_len;
|
|
1472
|
+
}
|
|
1473
|
+
*write++ = '}';
|
|
1474
|
+
remaining -= lit_len;
|
|
1475
|
+
read = end + 1;
|
|
1476
|
+
continue;
|
|
1477
|
+
}
|
|
1478
|
+
}
|
|
537
1479
|
|
|
538
1480
|
/* Try mmark syntax first if enabled */
|
|
539
1481
|
if (options->enable_mmark_index_syntax) {
|
|
@@ -541,9 +1483,9 @@ char *apex_process_index_entries(const char *text, apex_index_registry *registry
|
|
|
541
1483
|
}
|
|
542
1484
|
|
|
543
1485
|
/* Try TextIndex syntax if mmark didn't match and TextIndex is enabled */
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
consumed = parse_textindex(text, apex_idx_ptrdiff_to_int(read - text), apex_idx_size_to_int(text_len), &entry);
|
|
1486
|
+
if (!entry && options->enable_textindex_syntax && textindex_processing &&
|
|
1487
|
+
*read == '{' && read + 1 < text + text_len && read[1] == '^') {
|
|
1488
|
+
consumed = parse_textindex(text, apex_idx_ptrdiff_to_int(read - text), apex_idx_size_to_int(text_len), &entry, &bracketed_prefix, registry);
|
|
547
1489
|
}
|
|
548
1490
|
|
|
549
1491
|
/* Try Leanpub syntax if no match yet and Leanpub is enabled */
|
|
@@ -552,41 +1494,125 @@ char *apex_process_index_entries(const char *text, apex_index_registry *registry
|
|
|
552
1494
|
consumed = parse_leanpub_index(text, apex_idx_ptrdiff_to_int(read - text), apex_idx_size_to_int(text_len), &entry);
|
|
553
1495
|
}
|
|
554
1496
|
|
|
1497
|
+
if (consumed > 0 && !entry) {
|
|
1498
|
+
/* Unreferenced ##alias definition: strip brackets if any, drop mark */
|
|
1499
|
+
if (bracketed_prefix >= 2 && (write - output) >= bracketed_prefix) {
|
|
1500
|
+
char *span = write - bracketed_prefix;
|
|
1501
|
+
if (span[0] == '[' && span[bracketed_prefix - 1] == ']') {
|
|
1502
|
+
size_t inner_len = (size_t)bracketed_prefix - 2;
|
|
1503
|
+
if (inner_len > 0) {
|
|
1504
|
+
memmove(span, span + 1, inner_len);
|
|
1505
|
+
}
|
|
1506
|
+
write = span + inner_len;
|
|
1507
|
+
remaining += 2;
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1510
|
+
read += consumed;
|
|
1511
|
+
continue;
|
|
1512
|
+
}
|
|
1513
|
+
|
|
555
1514
|
if (entry && consumed > 0) {
|
|
1515
|
+
/* [term]{^}: [term] was already copied into output; strip the brackets,
|
|
1516
|
+
* keep the inner text, then append the index placeholder. */
|
|
1517
|
+
if (bracketed_prefix >= 2 && (write - output) >= bracketed_prefix) {
|
|
1518
|
+
char *span = write - bracketed_prefix;
|
|
1519
|
+
if (span[0] == '[' && span[bracketed_prefix - 1] == ']') {
|
|
1520
|
+
size_t inner_len = (size_t)bracketed_prefix - 2;
|
|
1521
|
+
if (inner_len > 0) {
|
|
1522
|
+
memmove(span, span + 1, inner_len);
|
|
1523
|
+
}
|
|
1524
|
+
write = span + inner_len;
|
|
1525
|
+
remaining += 2; /* freed [ and ] */
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1528
|
+
|
|
556
1529
|
/* Add entry to registry */
|
|
557
1530
|
entry->position = apex_idx_ptrdiff_to_int(read - text);
|
|
558
|
-
char anchor_id[64];
|
|
559
|
-
snprintf(anchor_id, sizeof(anchor_id), "idxref-%d", registry->next_ref_id);
|
|
560
|
-
entry->anchor_id = strdup(anchor_id);
|
|
561
|
-
entry->next = registry->entries;
|
|
562
|
-
registry->entries = entry;
|
|
563
|
-
registry->count++;
|
|
564
|
-
registry->next_ref_id++;
|
|
565
1531
|
|
|
566
|
-
/*
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
1532
|
+
/* Apply inbound (@ / @+) cross-refs onto their target entries */
|
|
1533
|
+
apex_index_xref **xptr = &entry->xrefs;
|
|
1534
|
+
while (*xptr) {
|
|
1535
|
+
apex_index_xref *x = *xptr;
|
|
1536
|
+
if (!x->inbound) {
|
|
1537
|
+
xptr = &(*xptr)->next;
|
|
1538
|
+
continue;
|
|
1539
|
+
}
|
|
570
1540
|
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
free(
|
|
578
|
-
|
|
579
|
-
|
|
1541
|
+
/* Find or create the external target entry */
|
|
1542
|
+
apex_index_entry *target = NULL;
|
|
1543
|
+
for (apex_index_entry *e = registry->entries; e; e = e->next) {
|
|
1544
|
+
char *ka = strip_html_tags(e->item);
|
|
1545
|
+
char *kb = strip_html_tags(x->target);
|
|
1546
|
+
bool same = ka && kb && strcasecmp(ka, kb) == 0 && !e->subitem;
|
|
1547
|
+
free(ka);
|
|
1548
|
+
free(kb);
|
|
1549
|
+
if (same) {
|
|
1550
|
+
target = e;
|
|
1551
|
+
break;
|
|
1552
|
+
}
|
|
580
1553
|
}
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
1554
|
+
if (!target) {
|
|
1555
|
+
target = apex_index_entry_new(x->target, APEX_INDEX_TEXTINDEX);
|
|
1556
|
+
if (target) {
|
|
1557
|
+
target->suppress_locator = true;
|
|
1558
|
+
target->position = entry->position;
|
|
1559
|
+
target->next = registry->entries;
|
|
1560
|
+
registry->entries = target;
|
|
1561
|
+
registry->count++;
|
|
1562
|
+
}
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1565
|
+
if (target) {
|
|
1566
|
+
apex_index_xref *rev = calloc(1, sizeof(apex_index_xref));
|
|
1567
|
+
if (rev) {
|
|
1568
|
+
rev->target = strdup(entry->item);
|
|
1569
|
+
rev->also = x->also;
|
|
1570
|
+
rev->inbound = false;
|
|
1571
|
+
rev->next = target->xrefs;
|
|
1572
|
+
target->xrefs = rev;
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
|
|
1576
|
+
*xptr = x->next;
|
|
1577
|
+
free(x->target);
|
|
1578
|
+
free(x);
|
|
584
1579
|
}
|
|
585
1580
|
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
1581
|
+
if (!entry->suppress_locator) {
|
|
1582
|
+
char anchor_id[64];
|
|
1583
|
+
snprintf(anchor_id, sizeof(anchor_id), "idxref-%d", registry->next_ref_id);
|
|
1584
|
+
entry->anchor_id = strdup(anchor_id);
|
|
1585
|
+
registry->next_ref_id++;
|
|
1586
|
+
|
|
1587
|
+
/* Replace mark with placeholder */
|
|
1588
|
+
size_t placeholder_len = strlen(INDEX_PLACEHOLDER_PREFIX) +
|
|
1589
|
+
strlen(anchor_id) +
|
|
1590
|
+
strlen(INDEX_PLACEHOLDER_SUFFIX);
|
|
1591
|
+
|
|
1592
|
+
if (remaining < placeholder_len + 1) {
|
|
1593
|
+
size_t used = write - output;
|
|
1594
|
+
capacity = (used + placeholder_len + 1) * 2;
|
|
1595
|
+
char *new_output = realloc(output, capacity);
|
|
1596
|
+
if (!new_output) {
|
|
1597
|
+
free(output);
|
|
1598
|
+
apex_index_entry_free(entry);
|
|
1599
|
+
return NULL;
|
|
1600
|
+
}
|
|
1601
|
+
output = new_output;
|
|
1602
|
+
write = output + used;
|
|
1603
|
+
remaining = capacity - used;
|
|
1604
|
+
}
|
|
1605
|
+
|
|
1606
|
+
snprintf(write, remaining, "%s%s%s",
|
|
1607
|
+
INDEX_PLACEHOLDER_PREFIX, anchor_id, INDEX_PLACEHOLDER_SUFFIX);
|
|
1608
|
+
write += placeholder_len;
|
|
1609
|
+
remaining -= placeholder_len;
|
|
1610
|
+
}
|
|
1611
|
+
/* see-type marks: consume without inserting a locator span */
|
|
1612
|
+
|
|
1613
|
+
entry->next = registry->entries;
|
|
1614
|
+
registry->entries = entry;
|
|
1615
|
+
registry->count++;
|
|
590
1616
|
|
|
591
1617
|
read += consumed;
|
|
592
1618
|
} else {
|
|
@@ -613,20 +1639,60 @@ char *apex_process_index_entries(const char *text, apex_index_registry *registry
|
|
|
613
1639
|
}
|
|
614
1640
|
|
|
615
1641
|
/**
|
|
616
|
-
*
|
|
1642
|
+
* Restore TextIndex caret placeholders left for literal marks in {^-} regions.
|
|
617
1643
|
*/
|
|
618
|
-
char *
|
|
619
|
-
if (!html || !
|
|
1644
|
+
static char *apex_restore_textindex_carets(const char *html) {
|
|
1645
|
+
if (!html || !strstr(html, TEXTINDEX_CARET_PLACEHOLDER)) {
|
|
620
1646
|
return NULL;
|
|
621
1647
|
}
|
|
622
1648
|
|
|
623
1649
|
size_t html_len = strlen(html);
|
|
624
|
-
size_t
|
|
1650
|
+
size_t ph_len = strlen(TEXTINDEX_CARET_PLACEHOLDER);
|
|
1651
|
+
size_t capacity = html_len + 1;
|
|
625
1652
|
char *output = malloc(capacity);
|
|
626
1653
|
if (!output) return NULL;
|
|
627
1654
|
|
|
628
1655
|
const char *read = html;
|
|
629
1656
|
char *write = output;
|
|
1657
|
+
while (*read) {
|
|
1658
|
+
if (strncmp(read, TEXTINDEX_CARET_PLACEHOLDER, ph_len) == 0) {
|
|
1659
|
+
*write++ = '^';
|
|
1660
|
+
read += ph_len;
|
|
1661
|
+
} else {
|
|
1662
|
+
*write++ = *read++;
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
*write = '\0';
|
|
1666
|
+
return output;
|
|
1667
|
+
}
|
|
1668
|
+
|
|
1669
|
+
/**
|
|
1670
|
+
* Render index markers in HTML output
|
|
1671
|
+
*/
|
|
1672
|
+
char *apex_render_index_markers(const char *html, apex_index_registry *registry, const apex_options *options) {
|
|
1673
|
+
if (!html || !options->enable_indices) {
|
|
1674
|
+
return NULL;
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1677
|
+
/* Always restore caret placeholders from {^-} protected literal marks */
|
|
1678
|
+
char *caret_restored = apex_restore_textindex_carets(html);
|
|
1679
|
+
const char *work = caret_restored ? caret_restored : html;
|
|
1680
|
+
|
|
1681
|
+
if (!registry || registry->count == 0) {
|
|
1682
|
+
/* No markers to expand; return caret restore if any */
|
|
1683
|
+
return caret_restored;
|
|
1684
|
+
}
|
|
1685
|
+
|
|
1686
|
+
size_t html_len = strlen(work);
|
|
1687
|
+
size_t capacity = html_len * 2;
|
|
1688
|
+
char *output = malloc(capacity);
|
|
1689
|
+
if (!output) {
|
|
1690
|
+
free(caret_restored);
|
|
1691
|
+
return NULL;
|
|
1692
|
+
}
|
|
1693
|
+
|
|
1694
|
+
const char *read = work;
|
|
1695
|
+
char *write = output;
|
|
630
1696
|
size_t remaining = capacity;
|
|
631
1697
|
|
|
632
1698
|
while (*read) {
|
|
@@ -655,6 +1721,7 @@ char *apex_render_index_markers(const char *html, apex_index_registry *registry,
|
|
|
655
1721
|
char *new_output = realloc(output, capacity);
|
|
656
1722
|
if (!new_output) {
|
|
657
1723
|
free(output);
|
|
1724
|
+
free(caret_restored);
|
|
658
1725
|
return NULL;
|
|
659
1726
|
}
|
|
660
1727
|
output = new_output;
|
|
@@ -679,6 +1746,7 @@ char *apex_render_index_markers(const char *html, apex_index_registry *registry,
|
|
|
679
1746
|
char *new_output = realloc(output, capacity);
|
|
680
1747
|
if (!new_output) {
|
|
681
1748
|
free(output);
|
|
1749
|
+
free(caret_restored);
|
|
682
1750
|
return NULL;
|
|
683
1751
|
}
|
|
684
1752
|
output = new_output;
|
|
@@ -690,6 +1758,7 @@ char *apex_render_index_markers(const char *html, apex_index_registry *registry,
|
|
|
690
1758
|
}
|
|
691
1759
|
|
|
692
1760
|
*write = '\0';
|
|
1761
|
+
free(caret_restored);
|
|
693
1762
|
return output;
|
|
694
1763
|
}
|
|
695
1764
|
|
|
@@ -700,40 +1769,139 @@ static int compare_index_entries(const void *a, const void *b) {
|
|
|
700
1769
|
const apex_index_entry *entry_a = *(const apex_index_entry **)a;
|
|
701
1770
|
const apex_index_entry *entry_b = *(const apex_index_entry **)b;
|
|
702
1771
|
|
|
703
|
-
|
|
704
|
-
|
|
1772
|
+
char *key_a = strip_html_tags(entry_a->item);
|
|
1773
|
+
char *key_b = strip_html_tags(entry_b->item);
|
|
1774
|
+
int item_cmp = strcasecmp(key_a ? key_a : "", key_b ? key_b : "");
|
|
1775
|
+
free(key_a);
|
|
1776
|
+
free(key_b);
|
|
705
1777
|
if (item_cmp != 0) return item_cmp;
|
|
706
1778
|
|
|
707
1779
|
/* If items are equal, compare subitems */
|
|
708
1780
|
if (entry_a->subitem && entry_b->subitem) {
|
|
709
|
-
|
|
1781
|
+
char *sa = strip_html_tags(entry_a->subitem);
|
|
1782
|
+
char *sb = strip_html_tags(entry_b->subitem);
|
|
1783
|
+
int sub_cmp = strcasecmp(sa ? sa : "", sb ? sb : "");
|
|
1784
|
+
free(sa);
|
|
1785
|
+
free(sb);
|
|
1786
|
+
if (sub_cmp != 0) return sub_cmp;
|
|
710
1787
|
} else if (entry_a->subitem) {
|
|
711
1788
|
return 1; /* Entry with subitem comes after entry without */
|
|
712
1789
|
} else if (entry_b->subitem) {
|
|
713
1790
|
return -1;
|
|
714
1791
|
}
|
|
715
1792
|
|
|
1793
|
+
/* Same heading: document order for locator merging */
|
|
1794
|
+
if (entry_a->position < entry_b->position) return -1;
|
|
1795
|
+
if (entry_a->position > entry_b->position) return 1;
|
|
716
1796
|
return 0;
|
|
717
1797
|
}
|
|
718
1798
|
|
|
1799
|
+
/**
|
|
1800
|
+
* True if two entries share the same main heading (for merging locators).
|
|
1801
|
+
*/
|
|
1802
|
+
static bool same_index_item(const apex_index_entry *a, const apex_index_entry *b) {
|
|
1803
|
+
if (!a || !b || !a->item || !b->item) return false;
|
|
1804
|
+
char *ka = strip_html_tags(a->item);
|
|
1805
|
+
char *kb = strip_html_tags(b->item);
|
|
1806
|
+
bool same = ka && kb && strcasecmp(ka, kb) == 0;
|
|
1807
|
+
free(ka);
|
|
1808
|
+
free(kb);
|
|
1809
|
+
return same;
|
|
1810
|
+
}
|
|
1811
|
+
|
|
1812
|
+
/**
|
|
1813
|
+
* True if two entries share the same subitem (both NULL counts as same).
|
|
1814
|
+
*/
|
|
1815
|
+
static bool same_index_subitem(const apex_index_entry *a, const apex_index_entry *b) {
|
|
1816
|
+
if (!a || !b) return false;
|
|
1817
|
+
if (!a->subitem && !b->subitem) return true;
|
|
1818
|
+
if (!a->subitem || !b->subitem) return false;
|
|
1819
|
+
char *sa = strip_html_tags(a->subitem);
|
|
1820
|
+
char *sb = strip_html_tags(b->subitem);
|
|
1821
|
+
bool same = sa && sb && strcasecmp(sa, sb) == 0;
|
|
1822
|
+
free(sa);
|
|
1823
|
+
free(sb);
|
|
1824
|
+
return same;
|
|
1825
|
+
}
|
|
1826
|
+
|
|
719
1827
|
/**
|
|
720
1828
|
* Get first letter of index term (for grouping)
|
|
721
1829
|
*/
|
|
722
1830
|
static char get_first_letter(const char *term) {
|
|
723
1831
|
if (!term || *term == '\0') return '?';
|
|
724
1832
|
|
|
725
|
-
/* Skip
|
|
726
|
-
while (*term
|
|
1833
|
+
/* Skip HTML tags, whitespace, and punctuation */
|
|
1834
|
+
while (*term) {
|
|
1835
|
+
if (*term == '<') {
|
|
1836
|
+
while (*term && *term != '>') term++;
|
|
1837
|
+
if (*term == '>') term++;
|
|
1838
|
+
continue;
|
|
1839
|
+
}
|
|
1840
|
+
if (isalnum((unsigned char)*term)) {
|
|
1841
|
+
return (char)toupper((unsigned char)*term);
|
|
1842
|
+
}
|
|
727
1843
|
term++;
|
|
728
1844
|
}
|
|
729
1845
|
|
|
730
|
-
if (*term) {
|
|
731
|
-
return toupper((unsigned char)*term);
|
|
732
|
-
}
|
|
733
|
-
|
|
734
1846
|
return '?';
|
|
735
1847
|
}
|
|
736
1848
|
|
|
1849
|
+
/**
|
|
1850
|
+
* Append TextIndex see / see-also run-in text for entries[start..end).
|
|
1851
|
+
* Deduplicates by (also, target). Uses APPEND macro from caller.
|
|
1852
|
+
*/
|
|
1853
|
+
#define APPEND_INDEX_XREFS(start, end) do { \
|
|
1854
|
+
bool _have_see = false, _have_also = false; \
|
|
1855
|
+
for (size_t _xi = (start); _xi < (end); _xi++) { \
|
|
1856
|
+
for (apex_index_xref *_x = entries[_xi]->xrefs; _x; _x = _x->next) { \
|
|
1857
|
+
if (_x->inbound || !_x->target) continue; \
|
|
1858
|
+
if (_x->also) _have_also = true; else _have_see = true; \
|
|
1859
|
+
} \
|
|
1860
|
+
} \
|
|
1861
|
+
if (_have_see) { \
|
|
1862
|
+
APPEND(". <em>See</em> "); \
|
|
1863
|
+
bool _first = true; \
|
|
1864
|
+
for (size_t _xi = (start); _xi < (end); _xi++) { \
|
|
1865
|
+
for (apex_index_xref *_x = entries[_xi]->xrefs; _x; _x = _x->next) { \
|
|
1866
|
+
if (_x->inbound || _x->also || !_x->target) continue; \
|
|
1867
|
+
bool _dup = false; \
|
|
1868
|
+
for (size_t _yj = (start); _yj <= _xi && !_dup; _yj++) { \
|
|
1869
|
+
for (apex_index_xref *_y = entries[_yj]->xrefs; _y; _y = _y->next) { \
|
|
1870
|
+
if (_y == _x) break; \
|
|
1871
|
+
if (!_y->inbound && !_y->also && _y->target && \
|
|
1872
|
+
strcasecmp(_y->target, _x->target) == 0) { _dup = true; break; } \
|
|
1873
|
+
} \
|
|
1874
|
+
} \
|
|
1875
|
+
if (_dup) continue; \
|
|
1876
|
+
if (!_first) APPEND("; "); \
|
|
1877
|
+
APPEND(_x->target); \
|
|
1878
|
+
_first = false; \
|
|
1879
|
+
} \
|
|
1880
|
+
} \
|
|
1881
|
+
} \
|
|
1882
|
+
if (_have_also) { \
|
|
1883
|
+
APPEND(_have_see ? ". <em>See also</em> " : ". <em>See also</em> "); \
|
|
1884
|
+
bool _first = true; \
|
|
1885
|
+
for (size_t _xi = (start); _xi < (end); _xi++) { \
|
|
1886
|
+
for (apex_index_xref *_x = entries[_xi]->xrefs; _x; _x = _x->next) { \
|
|
1887
|
+
if (_x->inbound || !_x->also || !_x->target) continue; \
|
|
1888
|
+
bool _dup = false; \
|
|
1889
|
+
for (size_t _yj = (start); _yj <= _xi && !_dup; _yj++) { \
|
|
1890
|
+
for (apex_index_xref *_y = entries[_yj]->xrefs; _y; _y = _y->next) { \
|
|
1891
|
+
if (_y == _x) break; \
|
|
1892
|
+
if (!_y->inbound && _y->also && _y->target && \
|
|
1893
|
+
strcasecmp(_y->target, _x->target) == 0) { _dup = true; break; } \
|
|
1894
|
+
} \
|
|
1895
|
+
} \
|
|
1896
|
+
if (_dup) continue; \
|
|
1897
|
+
if (!_first) APPEND("; "); \
|
|
1898
|
+
APPEND(_x->target); \
|
|
1899
|
+
_first = false; \
|
|
1900
|
+
} \
|
|
1901
|
+
} \
|
|
1902
|
+
} \
|
|
1903
|
+
} while (0)
|
|
1904
|
+
|
|
737
1905
|
/**
|
|
738
1906
|
* Generate index HTML from collected entries
|
|
739
1907
|
*/
|
|
@@ -797,7 +1965,7 @@ char *apex_generate_index_html(apex_index_registry *registry, const apex_options
|
|
|
797
1965
|
char current_letter = '\0';
|
|
798
1966
|
bool in_group = false;
|
|
799
1967
|
|
|
800
|
-
for (size_t i = 0; i < registry->count;
|
|
1968
|
+
for (size_t i = 0; i < registry->count; ) {
|
|
801
1969
|
apex_index_entry *entry = entries[i];
|
|
802
1970
|
char letter = get_first_letter(entry->item);
|
|
803
1971
|
|
|
@@ -814,35 +1982,95 @@ char *apex_generate_index_html(apex_index_registry *registry, const apex_options
|
|
|
814
1982
|
in_group = true;
|
|
815
1983
|
}
|
|
816
1984
|
|
|
817
|
-
/*
|
|
1985
|
+
/* Merge consecutive entries with the same main heading */
|
|
1986
|
+
size_t start = i;
|
|
1987
|
+
i++;
|
|
1988
|
+
while (i < registry->count && same_index_item(entries[start], entries[i])) {
|
|
1989
|
+
i++;
|
|
1990
|
+
}
|
|
1991
|
+
|
|
818
1992
|
char item_html[2048];
|
|
819
|
-
snprintf(item_html, sizeof(item_html), "<li>\n%s",
|
|
1993
|
+
snprintf(item_html, sizeof(item_html), "<li>\n%s", entries[start]->item);
|
|
820
1994
|
APPEND(item_html);
|
|
821
1995
|
|
|
822
|
-
|
|
823
|
-
|
|
1996
|
+
for (size_t li = start; li < i; li++) {
|
|
1997
|
+
if (entries[li]->subitem) continue;
|
|
1998
|
+
if (!entries[li]->anchor_id) continue;
|
|
1999
|
+
char link_html[256];
|
|
2000
|
+
if (entries[li]->primary) {
|
|
2001
|
+
snprintf(link_html, sizeof(link_html),
|
|
2002
|
+
" <strong><a class=\"index-return\" href=\"#%s\"><sup>[go]</sup></a></strong>",
|
|
2003
|
+
entries[li]->anchor_id);
|
|
2004
|
+
} else {
|
|
2005
|
+
snprintf(link_html, sizeof(link_html),
|
|
2006
|
+
" <a class=\"index-return\" href=\"#%s\"><sup>[go]</sup></a>",
|
|
2007
|
+
entries[li]->anchor_id);
|
|
2008
|
+
}
|
|
2009
|
+
APPEND(link_html);
|
|
824
2010
|
}
|
|
825
2011
|
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
2012
|
+
size_t j = start;
|
|
2013
|
+
bool opened_sub = false;
|
|
2014
|
+
while (j < i) {
|
|
2015
|
+
if (!entries[j]->subitem) {
|
|
2016
|
+
j++;
|
|
2017
|
+
continue;
|
|
2018
|
+
}
|
|
2019
|
+
if (!opened_sub) {
|
|
2020
|
+
APPEND("<ul>\n");
|
|
2021
|
+
opened_sub = true;
|
|
2022
|
+
}
|
|
2023
|
+
size_t sub_start = j;
|
|
2024
|
+
j++;
|
|
2025
|
+
while (j < i && entries[j]->subitem &&
|
|
2026
|
+
same_index_subitem(entries[sub_start], entries[j])) {
|
|
2027
|
+
j++;
|
|
2028
|
+
}
|
|
2029
|
+
APPEND("<li>\n");
|
|
2030
|
+
APPEND(entries[sub_start]->subitem);
|
|
2031
|
+
for (size_t k = sub_start; k < j; k++) {
|
|
2032
|
+
if (!entries[k]->anchor_id) continue;
|
|
2033
|
+
char link_html[256];
|
|
2034
|
+
snprintf(link_html, sizeof(link_html),
|
|
2035
|
+
" <a class=\"index-return\" href=\"#%s\"><sup>[go]</sup></a>",
|
|
2036
|
+
entries[k]->anchor_id);
|
|
2037
|
+
APPEND(link_html);
|
|
2038
|
+
}
|
|
2039
|
+
APPEND("</li>\n");
|
|
834
2040
|
}
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
2041
|
+
if (opened_sub) {
|
|
2042
|
+
/* Also-refs with sub-entries: append as subordinate list items */
|
|
2043
|
+
for (size_t xi = start; xi < i; xi++) {
|
|
2044
|
+
for (apex_index_xref *x = entries[xi]->xrefs; x; x = x->next) {
|
|
2045
|
+
if (x->inbound || !x->also || !x->target) continue;
|
|
2046
|
+
APPEND("<li>\n<em>See also</em> ");
|
|
2047
|
+
APPEND(x->target);
|
|
2048
|
+
APPEND("</li>\n");
|
|
2049
|
+
}
|
|
2050
|
+
}
|
|
2051
|
+
APPEND("</ul>\n");
|
|
2052
|
+
/* See-refs still run-in after the list */
|
|
2053
|
+
bool have_see = false;
|
|
2054
|
+
for (size_t xi = start; xi < i; xi++) {
|
|
2055
|
+
for (apex_index_xref *x = entries[xi]->xrefs; x; x = x->next) {
|
|
2056
|
+
if (!x->inbound && !x->also && x->target) have_see = true;
|
|
2057
|
+
}
|
|
2058
|
+
}
|
|
2059
|
+
if (have_see) {
|
|
2060
|
+
APPEND(". <em>See</em> ");
|
|
2061
|
+
bool first = true;
|
|
2062
|
+
for (size_t xi = start; xi < i; xi++) {
|
|
2063
|
+
for (apex_index_xref *x = entries[xi]->xrefs; x; x = x->next) {
|
|
2064
|
+
if (x->inbound || x->also || !x->target) continue;
|
|
2065
|
+
if (!first) APPEND("; ");
|
|
2066
|
+
APPEND(x->target);
|
|
2067
|
+
first = false;
|
|
2068
|
+
}
|
|
2069
|
+
}
|
|
2070
|
+
}
|
|
2071
|
+
} else {
|
|
2072
|
+
APPEND_INDEX_XREFS(start, i);
|
|
844
2073
|
}
|
|
845
|
-
|
|
846
2074
|
APPEND("</li>\n");
|
|
847
2075
|
}
|
|
848
2076
|
|
|
@@ -853,35 +2081,93 @@ char *apex_generate_index_html(apex_index_registry *registry, const apex_options
|
|
|
853
2081
|
/* Simple list without grouping */
|
|
854
2082
|
APPEND("<ul>\n");
|
|
855
2083
|
|
|
856
|
-
for (size_t i = 0; i < registry->count;
|
|
857
|
-
|
|
2084
|
+
for (size_t i = 0; i < registry->count; ) {
|
|
2085
|
+
size_t start = i;
|
|
2086
|
+
i++;
|
|
2087
|
+
while (i < registry->count && same_index_item(entries[start], entries[i])) {
|
|
2088
|
+
i++;
|
|
2089
|
+
}
|
|
858
2090
|
|
|
859
2091
|
char item_html[2048];
|
|
860
|
-
snprintf(item_html, sizeof(item_html), "<li>\n%s",
|
|
2092
|
+
snprintf(item_html, sizeof(item_html), "<li>\n%s", entries[start]->item);
|
|
861
2093
|
APPEND(item_html);
|
|
862
2094
|
|
|
863
|
-
|
|
864
|
-
|
|
2095
|
+
for (size_t li = start; li < i; li++) {
|
|
2096
|
+
if (entries[li]->subitem) continue;
|
|
2097
|
+
if (!entries[li]->anchor_id) continue;
|
|
2098
|
+
char link_html[256];
|
|
2099
|
+
if (entries[li]->primary) {
|
|
2100
|
+
snprintf(link_html, sizeof(link_html),
|
|
2101
|
+
" <strong><a class=\"index-return\" href=\"#%s\"><sup>[go]</sup></a></strong>",
|
|
2102
|
+
entries[li]->anchor_id);
|
|
2103
|
+
} else {
|
|
2104
|
+
snprintf(link_html, sizeof(link_html),
|
|
2105
|
+
" <a class=\"index-return\" href=\"#%s\"><sup>[go]</sup></a>",
|
|
2106
|
+
entries[li]->anchor_id);
|
|
2107
|
+
}
|
|
2108
|
+
APPEND(link_html);
|
|
865
2109
|
}
|
|
866
2110
|
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
2111
|
+
size_t j = start;
|
|
2112
|
+
bool opened_sub = false;
|
|
2113
|
+
while (j < i) {
|
|
2114
|
+
if (!entries[j]->subitem) {
|
|
2115
|
+
j++;
|
|
2116
|
+
continue;
|
|
2117
|
+
}
|
|
2118
|
+
if (!opened_sub) {
|
|
2119
|
+
APPEND("<ul>\n");
|
|
2120
|
+
opened_sub = true;
|
|
2121
|
+
}
|
|
2122
|
+
size_t sub_start = j;
|
|
2123
|
+
j++;
|
|
2124
|
+
while (j < i && entries[j]->subitem &&
|
|
2125
|
+
same_index_subitem(entries[sub_start], entries[j])) {
|
|
2126
|
+
j++;
|
|
2127
|
+
}
|
|
2128
|
+
APPEND("<li>\n");
|
|
2129
|
+
APPEND(entries[sub_start]->subitem);
|
|
2130
|
+
for (size_t k = sub_start; k < j; k++) {
|
|
2131
|
+
if (!entries[k]->anchor_id) continue;
|
|
2132
|
+
char link_html[256];
|
|
2133
|
+
snprintf(link_html, sizeof(link_html),
|
|
2134
|
+
" <a class=\"index-return\" href=\"#%s\"><sup>[go]</sup></a>",
|
|
2135
|
+
entries[k]->anchor_id);
|
|
2136
|
+
APPEND(link_html);
|
|
2137
|
+
}
|
|
2138
|
+
APPEND("</li>\n");
|
|
874
2139
|
}
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
2140
|
+
if (opened_sub) {
|
|
2141
|
+
for (size_t xi = start; xi < i; xi++) {
|
|
2142
|
+
for (apex_index_xref *x = entries[xi]->xrefs; x; x = x->next) {
|
|
2143
|
+
if (x->inbound || !x->also || !x->target) continue;
|
|
2144
|
+
APPEND("<li>\n<em>See also</em> ");
|
|
2145
|
+
APPEND(x->target);
|
|
2146
|
+
APPEND("</li>\n");
|
|
2147
|
+
}
|
|
2148
|
+
}
|
|
2149
|
+
APPEND("</ul>\n");
|
|
2150
|
+
bool have_see = false;
|
|
2151
|
+
for (size_t xi = start; xi < i; xi++) {
|
|
2152
|
+
for (apex_index_xref *x = entries[xi]->xrefs; x; x = x->next) {
|
|
2153
|
+
if (!x->inbound && !x->also && x->target) have_see = true;
|
|
2154
|
+
}
|
|
2155
|
+
}
|
|
2156
|
+
if (have_see) {
|
|
2157
|
+
APPEND(". <em>See</em> ");
|
|
2158
|
+
bool first = true;
|
|
2159
|
+
for (size_t xi = start; xi < i; xi++) {
|
|
2160
|
+
for (apex_index_xref *x = entries[xi]->xrefs; x; x = x->next) {
|
|
2161
|
+
if (x->inbound || x->also || !x->target) continue;
|
|
2162
|
+
if (!first) APPEND("; ");
|
|
2163
|
+
APPEND(x->target);
|
|
2164
|
+
first = false;
|
|
2165
|
+
}
|
|
2166
|
+
}
|
|
2167
|
+
}
|
|
2168
|
+
} else {
|
|
2169
|
+
APPEND_INDEX_XREFS(start, i);
|
|
883
2170
|
}
|
|
884
|
-
|
|
885
2171
|
APPEND("</li>\n");
|
|
886
2172
|
}
|
|
887
2173
|
|
|
@@ -891,6 +2177,7 @@ char *apex_generate_index_html(apex_index_registry *registry, const apex_options
|
|
|
891
2177
|
APPEND("</div>\n");
|
|
892
2178
|
|
|
893
2179
|
#undef APPEND
|
|
2180
|
+
#undef APPEND_INDEX_XREFS
|
|
894
2181
|
|
|
895
2182
|
*write = '\0';
|
|
896
2183
|
free(entries);
|