apex-ruby 1.0.6
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 +7 -0
- data/README.md +119 -0
- data/apex-ruby.gemspec +31 -0
- data/ext/apex_ext/apex_ext.c +215 -0
- data/ext/apex_ext/apex_src/BENCHMARK.md +32 -0
- data/ext/apex_ext/apex_src/BENCHMARK_COMPARISON.md +67 -0
- data/ext/apex_ext/apex_src/CHANGELOG.md +2454 -0
- data/ext/apex_ext/apex_src/CMakeLists.txt +454 -0
- data/ext/apex_ext/apex_src/Dockerfile.linux-build +15 -0
- data/ext/apex_ext/apex_src/Formula/apex.rb +38 -0
- data/ext/apex_ext/apex_src/Info.plist.in +27 -0
- data/ext/apex_ext/apex_src/LICENSE +21 -0
- data/ext/apex_ext/apex_src/Package.swift +160 -0
- data/ext/apex_ext/apex_src/PackageSupport/README.md +17 -0
- data/ext/apex_ext/apex_src/PackageSupport/cmark-gfm/cmark-gfm_export.h +20 -0
- data/ext/apex_ext/apex_src/PackageSupport/cmark-gfm/cmark-gfm_version.h +14 -0
- data/ext/apex_ext/apex_src/PackageSupport/cmark-gfm/cmark_gfm_spm_stub.c +4 -0
- data/ext/apex_ext/apex_src/PackageSupport/cmark-gfm/config.h +41 -0
- data/ext/apex_ext/apex_src/README.md +452 -0
- data/ext/apex_ext/apex_src/VERSION +1 -0
- data/ext/apex_ext/apex_src/apex-header-2-rb@2x.webp +0 -0
- data/ext/apex_ext/apex_src/apex-plugins.json.example +20 -0
- data/ext/apex_ext/apex_src/apex.pc.in +11 -0
- data/ext/apex_ext/apex_src/cli/main.c +2720 -0
- data/ext/apex_ext/apex_src/debug_test.sh +22 -0
- data/ext/apex_ext/apex_src/docs/API_REFERENCE.md +451 -0
- data/ext/apex_ext/apex_src/docs/ARCHITECTURE.md +166 -0
- data/ext/apex_ext/apex_src/docs/CMARK_INTEGRATION.md +220 -0
- data/ext/apex_ext/apex_src/docs/CRITICMARKUP.md +501 -0
- data/ext/apex_ext/apex_src/docs/DEBUGGING.md +73 -0
- data/ext/apex_ext/apex_src/docs/FINAL_STATUS.md +391 -0
- data/ext/apex_ext/apex_src/docs/FINAL_STATUS_UPDATE.md +237 -0
- data/ext/apex_ext/apex_src/docs/FUTURE_FEATURES.md +456 -0
- data/ext/apex_ext/apex_src/docs/IAL_FEATURES.md +210 -0
- data/ext/apex_ext/apex_src/docs/IAL_STATUS.md +344 -0
- data/ext/apex_ext/apex_src/docs/INTEGRATION_EXAMPLE.m +144 -0
- data/ext/apex_ext/apex_src/docs/LIMITATIONS_RESOLVED.md +278 -0
- data/ext/apex_ext/apex_src/docs/OUTPUT_MODES.md +321 -0
- data/ext/apex_ext/apex_src/docs/PROGRESS.md +167 -0
- data/ext/apex_ext/apex_src/docs/STANDALONE_FEATURE.md +174 -0
- data/ext/apex_ext/apex_src/docs/TABLE_SPANS_STATUS.md +243 -0
- data/ext/apex_ext/apex_src/docs/TEST_COVERAGE.md +316 -0
- data/ext/apex_ext/apex_src/docs/USER_GUIDE.md +803 -0
- data/ext/apex_ext/apex_src/docs/WIKI_LINKS_ISSUE.md +91 -0
- data/ext/apex_ext/apex_src/documentation/README.md +160 -0
- data/ext/apex_ext/apex_src/documentation/docsets/Apex Command Line Options.cheatsheet.txt +365 -0
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Info.plist +24 -0
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/C-API.html +1737 -0
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Citations.html +1420 -0
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Command-Line-Options.html +3574 -0
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Configuration.html +1603 -0
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Credits.html +910 -0
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Examples.html +1168 -0
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Getting-Started.html +1003 -0
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Header-IDs.html +1308 -0
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Home.html +1078 -0
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Inline-Attribute-Lists.html +1622 -0
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Installation.html +1168 -0
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Limitations-and-Roadmap.html +1698 -0
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Metadata-Transforms.html +1531 -0
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Modes.html +1980 -0
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Multi-File-Documents.html +1368 -0
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Pandoc-Integration.html +1151 -0
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Plugins.html +2861 -0
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Syntax.html +3981 -0
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Troubleshooting.html +1454 -0
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Usage.html +1200 -0
- data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Xcode-Integration.html +2066 -0
- 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/docsets/Apex.docset/Contents/Resources/tempOptimizedIndex.dsidx +0 -0
- data/ext/apex_ext/apex_src/documentation/docsets/ApexCLI.docset/Contents/Info.plist +22 -0
- data/ext/apex_ext/apex_src/documentation/docsets/ApexCLI.docset/Contents/Resources/Documents/cheatset_resources/Open_Sans.woff +0 -0
- data/ext/apex_ext/apex_src/documentation/docsets/ApexCLI.docset/Contents/Resources/Documents/cheatset_resources/Open_Sans_Bold.woff +0 -0
- data/ext/apex_ext/apex_src/documentation/docsets/ApexCLI.docset/Contents/Resources/Documents/cheatset_resources/Open_Sans_Bold_Italic.woff +0 -0
- data/ext/apex_ext/apex_src/documentation/docsets/ApexCLI.docset/Contents/Resources/Documents/cheatset_resources/Open_Sans_Extrabold.woff +0 -0
- data/ext/apex_ext/apex_src/documentation/docsets/ApexCLI.docset/Contents/Resources/Documents/cheatset_resources/Open_Sans_Extrabold_Italic.woff +0 -0
- data/ext/apex_ext/apex_src/documentation/docsets/ApexCLI.docset/Contents/Resources/Documents/cheatset_resources/Open_Sans_Italic.woff +0 -0
- data/ext/apex_ext/apex_src/documentation/docsets/ApexCLI.docset/Contents/Resources/Documents/cheatset_resources/Open_Sans_Semibold.woff +0 -0
- data/ext/apex_ext/apex_src/documentation/docsets/ApexCLI.docset/Contents/Resources/Documents/cheatset_resources/Open_Sans_Semibold_Italic.woff +0 -0
- data/ext/apex_ext/apex_src/documentation/docsets/ApexCLI.docset/Contents/Resources/Documents/index.html +914 -0
- data/ext/apex_ext/apex_src/documentation/docsets/ApexCLI.docset/Contents/Resources/Documents/style.css +399 -0
- data/ext/apex_ext/apex_src/documentation/docsets/ApexCLI.docset/Contents/Resources/docSet.dsidx +0 -0
- data/ext/apex_ext/apex_src/documentation/docsets/ApexCLI.docset/Contents/Resources/optimizedIndex.dsidx +0 -0
- data/ext/apex_ext/apex_src/documentation/generate_app_docs.rb +772 -0
- data/ext/apex_ext/apex_src/documentation/generate_app_docs_ai.rb +678 -0
- data/ext/apex_ext/apex_src/documentation/generate_docset.rb +873 -0
- data/ext/apex_ext/apex_src/documentation/generate_single_html.rb +733 -0
- data/ext/apex_ext/apex_src/documentation/html/apex-docs.html +17073 -0
- data/ext/apex_ext/apex_src/documentation/shared_scripts.js +64 -0
- data/ext/apex_ext/apex_src/documentation/shared_styles.css +646 -0
- data/ext/apex_ext/apex_src/documentation/transform_for_app.example.md +260 -0
- data/ext/apex_ext/apex_src/examples/bracketed_spans_demo.md +119 -0
- data/ext/apex_ext/apex_src/examples/emoji_span_plugin.yml +11 -0
- data/ext/apex_ext/apex_src/examples/example.html +53 -0
- data/ext/apex_ext/apex_src/examples/example.md +85 -0
- data/ext/apex_ext/apex_src/examples/fenced_divs_demo.md +158 -0
- data/ext/apex_ext/apex_src/examples/kbd.md +8 -0
- data/ext/apex_ext/apex_src/examples/kbd_plugin.rb +250 -0
- data/ext/apex_ext/apex_src/examples/kbd_plugin.yml +9 -0
- data/ext/apex_ext/apex_src/icon/apexicon-outline-black.png +0 -0
- data/ext/apex_ext/apex_src/icon/apexicon-outline-black@2x.png +0 -0
- data/ext/apex_ext/apex_src/icon/apexicon-outline-mark.png +0 -0
- data/ext/apex_ext/apex_src/icon/apexicon-outline-mark@2x.png +0 -0
- data/ext/apex_ext/apex_src/icon/apexicon-outline-white.png +0 -0
- data/ext/apex_ext/apex_src/icon/apexicon-outline-white@2x.png +0 -0
- data/ext/apex_ext/apex_src/icon/apexicon.png +0 -0
- data/ext/apex_ext/apex_src/icon/apexicon@2x.png +0 -0
- data/ext/apex_ext/apex_src/include/apex/apex.h +247 -0
- data/ext/apex_ext/apex_src/include/apex/buffer.h +93 -0
- data/ext/apex_ext/apex_src/include/apex/module.modulemap +16 -0
- data/ext/apex_ext/apex_src/include/apex/parser.h +150 -0
- data/ext/apex_ext/apex_src/include/apex/renderer.h +39 -0
- data/ext/apex_ext/apex_src/man/apex-config.5 +374 -0
- data/ext/apex_ext/apex_src/man/apex-config.5.md +260 -0
- data/ext/apex_ext/apex_src/man/apex-plugins.7 +456 -0
- data/ext/apex_ext/apex_src/man/apex-plugins.7.md +365 -0
- data/ext/apex_ext/apex_src/man/apex.1 +828 -0
- data/ext/apex_ext/apex_src/man/apex.1.md +643 -0
- data/ext/apex_ext/apex_src/man/apex.1.new +338 -0
- data/ext/apex_ext/apex_src/objc/Apex.swift +237 -0
- data/ext/apex_ext/apex_src/objc/NSString+Apex.h +117 -0
- data/ext/apex_ext/apex_src/objc/NSString+Apex.m +332 -0
- data/ext/apex_ext/apex_src/src/_README.md +358 -0
- data/ext/apex_ext/apex_src/src/apex.c +6326 -0
- data/ext/apex_ext/apex_src/src/buffer.c +93 -0
- data/ext/apex_ext/apex_src/src/extensions/abbreviations.c +362 -0
- data/ext/apex_ext/apex_src/src/extensions/abbreviations.h +45 -0
- data/ext/apex_ext/apex_src/src/extensions/advanced_footnotes.c +184 -0
- data/ext/apex_ext/apex_src/src/extensions/advanced_footnotes.h +50 -0
- data/ext/apex_ext/apex_src/src/extensions/advanced_tables.c +1897 -0
- data/ext/apex_ext/apex_src/src/extensions/advanced_tables.h +42 -0
- data/ext/apex_ext/apex_src/src/extensions/callouts.c +215 -0
- data/ext/apex_ext/apex_src/src/extensions/callouts.h +53 -0
- data/ext/apex_ext/apex_src/src/extensions/citations.c +2042 -0
- data/ext/apex_ext/apex_src/src/extensions/citations.h +163 -0
- data/ext/apex_ext/apex_src/src/extensions/critic.c +329 -0
- data/ext/apex_ext/apex_src/src/extensions/critic.h +48 -0
- data/ext/apex_ext/apex_src/src/extensions/definition_list.c +1670 -0
- data/ext/apex_ext/apex_src/src/extensions/definition_list.h +42 -0
- data/ext/apex_ext/apex_src/src/extensions/emoji.c +710 -0
- data/ext/apex_ext/apex_src/src/extensions/emoji.h +38 -0
- data/ext/apex_ext/apex_src/src/extensions/emoji_data.h +942 -0
- data/ext/apex_ext/apex_src/src/extensions/fenced_divs.c +925 -0
- data/ext/apex_ext/apex_src/src/extensions/fenced_divs.h +43 -0
- data/ext/apex_ext/apex_src/src/extensions/github-emoji.txt +869 -0
- data/ext/apex_ext/apex_src/src/extensions/grid_tables.c +1121 -0
- data/ext/apex_ext/apex_src/src/extensions/grid_tables.h +33 -0
- data/ext/apex_ext/apex_src/src/extensions/header_ids.c +626 -0
- data/ext/apex_ext/apex_src/src/extensions/header_ids.h +60 -0
- data/ext/apex_ext/apex_src/src/extensions/highlight.c +135 -0
- data/ext/apex_ext/apex_src/src/extensions/highlight.h +16 -0
- data/ext/apex_ext/apex_src/src/extensions/html_markdown.c +408 -0
- data/ext/apex_ext/apex_src/src/extensions/html_markdown.h +42 -0
- data/ext/apex_ext/apex_src/src/extensions/ial.c +4084 -0
- data/ext/apex_ext/apex_src/src/extensions/ial.h +145 -0
- data/ext/apex_ext/apex_src/src/extensions/includes.c +1536 -0
- data/ext/apex_ext/apex_src/src/extensions/includes.h +54 -0
- data/ext/apex_ext/apex_src/src/extensions/index.c +967 -0
- data/ext/apex_ext/apex_src/src/extensions/index.h +90 -0
- data/ext/apex_ext/apex_src/src/extensions/inline_footnotes.c +205 -0
- data/ext/apex_ext/apex_src/src/extensions/inline_footnotes.h +34 -0
- data/ext/apex_ext/apex_src/src/extensions/inline_tables.c +332 -0
- data/ext/apex_ext/apex_src/src/extensions/inline_tables.h +13 -0
- data/ext/apex_ext/apex_src/src/extensions/insert.c +248 -0
- data/ext/apex_ext/apex_src/src/extensions/insert.h +18 -0
- data/ext/apex_ext/apex_src/src/extensions/math.c +279 -0
- data/ext/apex_ext/apex_src/src/extensions/math.h +32 -0
- data/ext/apex_ext/apex_src/src/extensions/metadata.c +3046 -0
- data/ext/apex_ext/apex_src/src/extensions/metadata.h +125 -0
- data/ext/apex_ext/apex_src/src/extensions/relaxed_tables.c +1297 -0
- data/ext/apex_ext/apex_src/src/extensions/relaxed_tables.h +39 -0
- data/ext/apex_ext/apex_src/src/extensions/special_markers.c +194 -0
- data/ext/apex_ext/apex_src/src/extensions/special_markers.h +29 -0
- data/ext/apex_ext/apex_src/src/extensions/sup_sub.c +405 -0
- data/ext/apex_ext/apex_src/src/extensions/sup_sub.h +16 -0
- data/ext/apex_ext/apex_src/src/extensions/syntax_highlight.c +468 -0
- data/ext/apex_ext/apex_src/src/extensions/syntax_highlight.h +44 -0
- data/ext/apex_ext/apex_src/src/extensions/table_html_postprocess.c +2679 -0
- data/ext/apex_ext/apex_src/src/extensions/table_html_postprocess.h +23 -0
- data/ext/apex_ext/apex_src/src/extensions/toc.c +255 -0
- data/ext/apex_ext/apex_src/src/extensions/toc.h +34 -0
- data/ext/apex_ext/apex_src/src/extensions/wiki_links.c +624 -0
- data/ext/apex_ext/apex_src/src/extensions/wiki_links.h +58 -0
- data/ext/apex_ext/apex_src/src/html_renderer.c +2762 -0
- data/ext/apex_ext/apex_src/src/html_renderer.h +126 -0
- data/ext/apex_ext/apex_src/src/parser.c +227 -0
- data/ext/apex_ext/apex_src/src/plugins.c +895 -0
- data/ext/apex_ext/apex_src/src/plugins.h +39 -0
- data/ext/apex_ext/apex_src/src/plugins_env.c +187 -0
- data/ext/apex_ext/apex_src/src/plugins_remote.c +263 -0
- data/ext/apex_ext/apex_src/src/pretty_html.c +358 -0
- data/ext/apex_ext/apex_src/src/renderer.c +241 -0
- data/ext/apex_ext/apex_src/src/utf8.c +56 -0
- data/ext/apex_ext/apex_src/test-linux-build.sh +20 -0
- data/ext/apex_ext/apex_src/test.html +103 -0
- data/ext/apex_ext/apex_src/test_coverage.sh +121 -0
- data/ext/apex_ext/apex_src/test_ial_fenced.md +6 -0
- data/ext/apex_ext/apex_src/test_math_norm.py +79 -0
- data/ext/apex_ext/apex_src/test_pandoc_output.html +48 -0
- data/ext/apex_ext/apex_src/test_spm.sh +107 -0
- data/ext/apex_ext/apex_src/tests/ApexSPMTest/main.swift +50 -0
- data/ext/apex_ext/apex_src/tests/BENCHMARK_RESULTS.md +229 -0
- data/ext/apex_ext/apex_src/tests/CMakeLists.txt +24 -0
- data/ext/apex_ext/apex_src/tests/README.md +146 -0
- data/ext/apex_ext/apex_src/tests/benchmark.sh +113 -0
- data/ext/apex_ext/apex_src/tests/benchmark_comparison.sh +166 -0
- data/ext/apex_ext/apex_src/tests/compare_header_ids.sh +31 -0
- data/ext/apex_ext/apex_src/tests/fixtures/basic/headers.md +25 -0
- data/ext/apex_ext/apex_src/tests/fixtures/basic/list-interruption.md +24 -0
- data/ext/apex_ext/apex_src/tests/fixtures/basic/misc_markup.md +33 -0
- data/ext/apex_ext/apex_src/tests/fixtures/basic/test_basic.md +26 -0
- data/ext/apex_ext/apex_src/tests/fixtures/code/code-blocks.md +260 -0
- data/ext/apex_ext/apex_src/tests/fixtures/combine_summary/SUMMARY.md +6 -0
- data/ext/apex_ext/apex_src/tests/fixtures/combine_summary/chapter1.md +7 -0
- data/ext/apex_ext/apex_src/tests/fixtures/combine_summary/index.txt +9 -0
- data/ext/apex_ext/apex_src/tests/fixtures/combine_summary/intro.md +5 -0
- data/ext/apex_ext/apex_src/tests/fixtures/combine_summary/section1_1.md +5 -0
- data/ext/apex_ext/apex_src/tests/fixtures/comprehensive_test.md +620 -0
- data/ext/apex_ext/apex_src/tests/fixtures/debug_ref_image_ial.md +3 -0
- data/ext/apex_ext/apex_src/tests/fixtures/demos/ial.md +11 -0
- data/ext/apex_ext/apex_src/tests/fixtures/demos/ial_demo.md +177 -0
- data/ext/apex_ext/apex_src/tests/fixtures/extensions/emoji-autocorrect.md +94 -0
- data/ext/apex_ext/apex_src/tests/fixtures/extensions/emoji_test.md +3 -0
- data/ext/apex_ext/apex_src/tests/fixtures/extensions/kbd_test.md +3 -0
- data/ext/apex_ext/apex_src/tests/fixtures/ial/bracketed_spans_test.md +74 -0
- data/ext/apex_ext/apex_src/tests/fixtures/images/image_and_encoding_test.md +27 -0
- data/ext/apex_ext/apex_src/tests/fixtures/images/multimarkdown_image_attributes_test.md +60 -0
- data/ext/apex_ext/apex_src/tests/fixtures/images/pandoc_ial_image_test.md +27 -0
- data/ext/apex_ext/apex_src/tests/fixtures/images/width_height_conversion_test.md +94 -0
- data/ext/apex_ext/apex_src/tests/fixtures/img-in-div.md +16 -0
- data/ext/apex_ext/apex_src/tests/fixtures/includes/code.py +4 -0
- data/ext/apex_ext/apex_src/tests/fixtures/includes/data.csv +5 -0
- data/ext/apex_ext/apex_src/tests/fixtures/includes/data.tsv +5 -0
- data/ext/apex_ext/apex_src/tests/fixtures/includes/image.png +2 -0
- data/ext/apex_ext/apex_src/tests/fixtures/includes/metadata_options.yml +11 -0
- data/ext/apex_ext/apex_src/tests/fixtures/includes/nested.md +8 -0
- data/ext/apex_ext/apex_src/tests/fixtures/includes/raw.html +4 -0
- data/ext/apex_ext/apex_src/tests/fixtures/includes/simple.md +7 -0
- data/ext/apex_ext/apex_src/tests/fixtures/includes/test_image.png +0 -0
- data/ext/apex_ext/apex_src/tests/fixtures/large_doc.md +1094 -0
- data/ext/apex_ext/apex_src/tests/fixtures/metadata_options.yml +11 -0
- data/ext/apex_ext/apex_src/tests/fixtures/output/gfm_header_id_test.md +96 -0
- data/ext/apex_ext/apex_src/tests/fixtures/output/test_citations.md +43 -0
- data/ext/apex_ext/apex_src/tests/fixtures/output/test_def_list_links.md +12 -0
- data/ext/apex_ext/apex_src/tests/fixtures/output/test_index_mmark.md +53 -0
- data/ext/apex_ext/apex_src/tests/fixtures/output/test_index_textindex.md +37 -0
- data/ext/apex_ext/apex_src/tests/fixtures/tables/advanced_tables_test.md +93 -0
- data/ext/apex_ext/apex_src/tests/fixtures/tables/inline_tables_test.md +38 -0
- data/ext/apex_ext/apex_src/tests/fixtures/tables/relaxed-table.md +12 -0
- data/ext/apex_ext/apex_src/tests/fixtures/tables/table_cr_line_endings.md +15 -0
- data/ext/apex_ext/apex_src/tests/fixtures/tables/table_no_trailing_newline.md +15 -0
- data/ext/apex_ext/apex_src/tests/generate_gfm_ids.sh +105 -0
- data/ext/apex_ext/apex_src/tests/generate_ial_demo.sh +143 -0
- data/ext/apex_ext/apex_src/tests/gfm_id_comparison_summary.md +96 -0
- data/ext/apex_ext/apex_src/tests/gh_api_test.md +6 -0
- data/ext/apex_ext/apex_src/tests/ial_demo.html +186 -0
- data/ext/apex_ext/apex_src/tests/include_code.py +19 -0
- data/ext/apex_ext/apex_src/tests/include_snippet.md +15 -0
- data/ext/apex_ext/apex_src/tests/multi_file_cli_test.sh +64 -0
- data/ext/apex_ext/apex_src/tests/sample_data.csv +7 -0
- data/ext/apex_ext/apex_src/tests/table_escaped_ltlt.md +4 -0
- data/ext/apex_ext/apex_src/tests/test_basic.c +74 -0
- data/ext/apex_ext/apex_src/tests/test_extensions.c +2116 -0
- data/ext/apex_ext/apex_src/tests/test_helpers.c +183 -0
- data/ext/apex_ext/apex_src/tests/test_helpers.h +91 -0
- data/ext/apex_ext/apex_src/tests/test_ial.c +282 -0
- data/ext/apex_ext/apex_src/tests/test_links.c +418 -0
- data/ext/apex_ext/apex_src/tests/test_marked_integration.c +265 -0
- data/ext/apex_ext/apex_src/tests/test_metadata.c +908 -0
- data/ext/apex_ext/apex_src/tests/test_output.c +1118 -0
- data/ext/apex_ext/apex_src/tests/test_plugins.c +219 -0
- data/ext/apex_ext/apex_src/tests/test_refs.bib +31 -0
- data/ext/apex_ext/apex_src/tests/test_runner.c +244 -0
- data/ext/apex_ext/apex_src/tests/test_syntax_highlight.c +198 -0
- data/ext/apex_ext/apex_src/tests/test_tables.c +862 -0
- data/ext/apex_ext/apex_src/tests/update_benchmarks.sh +9 -0
- data/ext/apex_ext/apex_src/tests/yaml_test.md +13 -0
- data/ext/apex_ext/apex_src/tests.rb +39 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/CMakeLists.txt +48 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/COPYING +170 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/CheckFileOffsetBits.c +14 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/CheckFileOffsetBits.cmake +43 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/FindAsan.cmake +74 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/Makefile.nmake +38 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/README.md +206 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/api_test/CMakeLists.txt +30 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/api_test/cplusplus.cpp +15 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/api_test/cplusplus.h +16 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/api_test/harness.c +111 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/api_test/harness.h +35 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/api_test/main.c +1169 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/appveyor.yml +21 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/bench/samples/block-bq-flat.md +16 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/bench/samples/block-bq-nested.md +13 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/bench/samples/block-code.md +11 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/bench/samples/block-fences.md +14 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/bench/samples/block-heading.md +9 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/bench/samples/block-hr.md +10 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/bench/samples/block-html.md +32 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/bench/samples/block-lheading.md +8 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/bench/samples/block-list-flat.md +67 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/bench/samples/block-list-nested.md +36 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/bench/samples/block-ref-flat.md +15 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/bench/samples/block-ref-nested.md +17 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/bench/samples/inline-autolink.md +14 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/bench/samples/inline-backticks.md +3 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/bench/samples/inline-em-flat.md +5 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/bench/samples/inline-em-nested.md +5 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/bench/samples/inline-em-worst.md +5 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/bench/samples/inline-entity.md +11 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/bench/samples/inline-escape.md +15 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/bench/samples/inline-html.md +44 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/bench/samples/inline-links-flat.md +23 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/bench/samples/inline-links-nested.md +13 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/bench/samples/inline-newlines.md +24 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/bench/samples/lorem1.md +13 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/bench/samples/rawtabs.md +18 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/bench/statistics.py +595 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/bench/stats.py +19 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/benchmarks.md +33 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/changelog.txt +1245 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/data/CaseFolding.txt +1495 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/extensions/CMakeLists.txt +119 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/extensions/autolink.c +508 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/extensions/autolink.h +8 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/extensions/cmark-gfm-core-extensions.h +54 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/extensions/core-extensions.c +27 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/extensions/ext_scanners.c +879 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/extensions/ext_scanners.h +24 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/extensions/ext_scanners.re +92 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/extensions/strikethrough.c +167 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/extensions/strikethrough.h +9 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/extensions/table.c +917 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/extensions/table.h +12 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/extensions/tagfilter.c +60 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/extensions/tagfilter.h +8 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/extensions/tasklist.c +156 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/extensions/tasklist.h +8 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/fuzz/CMakeLists.txt +22 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/fuzz/README.md +12 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/fuzz/fuzz_quadratic.c +91 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/fuzz/fuzz_quadratic_brackets.c +110 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/fuzz/fuzzloop.sh +28 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/man/CMakeLists.txt +10 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/man/make_man_page.py +133 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/man/man1/cmark-gfm.1 +78 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/man/man3/cmark-gfm.3 +1041 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/nmake.bat +1 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/CMakeLists.txt +230 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/arena.c +104 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/blocks.c +1622 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/buffer.c +278 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/buffer.h +116 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/case_fold_switch.inc +4327 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/chunk.h +135 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/cmark-gfm-extension_api.h +737 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/cmark-gfm.h +833 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/cmark-gfm_version.h.in +7 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/cmark.c +55 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/cmark_ctype.c +44 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/cmark_ctype.h +33 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/commonmark.c +514 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/config.h.in +76 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/entities.inc +2138 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/footnotes.c +63 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/footnotes.h +27 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/houdini.h +57 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/houdini_href_e.c +100 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/houdini_html_e.c +66 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/houdini_html_u.c +149 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/html.c +502 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/html.h +27 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/inlines.c +1788 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/inlines.h +29 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/iterator.c +159 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/iterator.h +26 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/latex.c +468 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/libcmark-gfm.pc.in +10 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/linked_list.c +37 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/main.c +328 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/man.c +274 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/map.c +129 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/map.h +44 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/node.c +1045 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/node.h +167 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/parser.h +59 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/plaintext.c +218 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/plugin.c +36 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/plugin.h +34 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/references.c +43 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/references.h +26 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/registry.c +63 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/registry.h +24 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/render.c +213 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/render.h +62 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/scanners.c +14056 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/scanners.h +70 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/scanners.re +365 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/syntax_extension.c +149 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/syntax_extension.h +34 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/utf8.c +317 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/utf8.h +35 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/xml.c +182 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/suppressions +10 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/test/CMakeLists.txt +114 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/test/afl_test_cases/test.md +49 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/test/cmark-fuzz.c +58 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/test/cmark.py +105 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/test/entity_tests.py +67 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/test/extensions-full-info-string.txt +0 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/test/extensions-table-prefer-style-attributes.txt +38 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/test/extensions.txt +920 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/test/fuzzing_dictionary +67 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/test/normalize.py +194 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/test/pathological_tests.py +160 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/test/regression.txt +375 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/test/roundtrip_tests.py +50 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/test/run-cmark-fuzz +4 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/test/smart_punct.txt +177 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/test/spec.txt +10212 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/test/spec_tests.py +152 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/toolchain-mingw32.cmake +17 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/tools/Dockerfile +41 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/tools/appveyor-build.bat +13 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/tools/make_entities_inc.py +32 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/tools/mkcasefold.pl +22 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/tools/xml2md.xsl +319 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/tools/xml2md_gfm.xsl +80 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/why-cmark-and-not-x.md +104 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/wrappers/wrapper.js +6 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/wrappers/wrapper.py +37 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/wrappers/wrapper.rb +15 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/wrappers/wrapper.rkt +208 -0
- data/ext/apex_ext/apex_src/vendor/cmark-gfm/wrappers/wrapper_ext.py +109 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/CMakeLists.txt +160 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/Changes +372 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/License +20 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/Makefile.am +51 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/ReadMe.md +46 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/announcement.msg +89 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/bootstrap +3 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/cmake/config.h.in +4 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/configure.ac +73 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/doc/doxygen.cfg +222 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/docker/README.mkd +17 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/docker/alpine-3.7 +26 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/docker/fedora-25 +26 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/docker/ubuntu-14.04 +29 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/docker/ubuntu-16.04 +24 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/examples/anchors.yaml +10 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/examples/array.yaml +2 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/examples/global-tag.yaml +14 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/examples/json.yaml +1 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/examples/mapping.yaml +2 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/examples/numbers.yaml +1 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/examples/strings.yaml +7 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/examples/tags.yaml +7 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/examples/yaml-version.yaml +3 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/include/Makefile.am +17 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/include/yaml.h +1999 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/pkg/ReadMe.md +77 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/pkg/docker/Dockerfile +32 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/pkg/docker/output/ReadMe +1 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/pkg/docker/scripts/libyaml-dist.sh +23 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/regression-inputs/clusterfuzz-testcase-minimized-5607885063061504.yml +1 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/src/Makefile.am +4 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/src/api.c +1393 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/src/dumper.c +394 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/src/emitter.c +2358 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/src/loader.c +544 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/src/parser.c +1416 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/src/reader.c +469 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/src/scanner.c +3598 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/src/writer.c +141 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/src/yaml_private.h +684 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/tests/CMakeLists.txt +27 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/tests/Makefile.am +9 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/tests/ReadMe.md +63 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/tests/example-deconstructor-alt.c +800 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/tests/example-deconstructor.c +1127 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/tests/example-reformatter-alt.c +217 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/tests/example-reformatter.c +202 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/tests/run-all-tests.sh +29 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/tests/run-dumper.c +314 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/tests/run-emitter-test-suite.c +290 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/tests/run-emitter.c +327 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/tests/run-loader.c +63 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/tests/run-parser-test-suite.c +196 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/tests/run-parser.c +88 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/tests/run-scanner.c +63 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/tests/test-reader.c +354 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/tests/test-version.c +29 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/yaml-0.1.pc.in +10 -0
- data/ext/apex_ext/apex_src/vendor/libyaml/yamlConfig.cmake.in +16 -0
- data/ext/apex_ext/extconf.rb +103 -0
- data/lib/apex/configurable.rb +46 -0
- data/lib/apex/document.rb +66 -0
- data/lib/apex/version.rb +15 -0
- data/lib/apex.rb +28 -0
- metadata +544 -0
|
@@ -0,0 +1,3046 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Metadata Extension for Apex
|
|
3
|
+
* Implementation (Simplified version - metadata handled via preprocessing)
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
#include "../../include/apex/apex.h"
|
|
7
|
+
#include "metadata.h"
|
|
8
|
+
#include <string.h>
|
|
9
|
+
#include <stdlib.h>
|
|
10
|
+
#include <ctype.h>
|
|
11
|
+
#include <stdio.h>
|
|
12
|
+
#include <stdbool.h>
|
|
13
|
+
#include <stdarg.h>
|
|
14
|
+
#include <time.h>
|
|
15
|
+
#include <regex.h>
|
|
16
|
+
|
|
17
|
+
#ifdef APEX_HAVE_LIBYAML
|
|
18
|
+
#include <yaml.h>
|
|
19
|
+
#endif
|
|
20
|
+
#ifdef _POSIX_C_SOURCE
|
|
21
|
+
/* strptime should be available if POSIX is defined */
|
|
22
|
+
#elif defined(__APPLE__) || defined(__linux__)
|
|
23
|
+
/* strptime is available on macOS and most Linux systems */
|
|
24
|
+
#define HAVE_STRPTIME 1
|
|
25
|
+
#elif defined(_GNU_SOURCE)
|
|
26
|
+
/* GNU extension */
|
|
27
|
+
#define HAVE_STRPTIME 1
|
|
28
|
+
#endif
|
|
29
|
+
|
|
30
|
+
/* For now, we'll handle metadata as a preprocessing step rather than a block type
|
|
31
|
+
* This is simpler and matches how MultiMarkdown actually works */
|
|
32
|
+
|
|
33
|
+
/* Node type for metadata blocks */
|
|
34
|
+
/* Note: APEX_NODE_METADATA is defined as an enum in parser.h, not as a variable */
|
|
35
|
+
|
|
36
|
+
/* Transform structures */
|
|
37
|
+
typedef struct apex_transform {
|
|
38
|
+
char *name;
|
|
39
|
+
char *options; /* NULL if no options */
|
|
40
|
+
struct apex_transform *next;
|
|
41
|
+
} apex_transform;
|
|
42
|
+
|
|
43
|
+
/* Dynamic array for string arrays (used by split/join/slice) */
|
|
44
|
+
typedef struct {
|
|
45
|
+
char **items;
|
|
46
|
+
size_t count;
|
|
47
|
+
size_t capacity;
|
|
48
|
+
} apex_string_array;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Free metadata items list
|
|
52
|
+
*/
|
|
53
|
+
void apex_free_metadata(apex_metadata_item *metadata) {
|
|
54
|
+
while (metadata) {
|
|
55
|
+
apex_metadata_item *next = metadata->next;
|
|
56
|
+
free(metadata->key);
|
|
57
|
+
free(metadata->value);
|
|
58
|
+
free(metadata);
|
|
59
|
+
metadata = next;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Trim whitespace from both ends of a string (in-place)
|
|
65
|
+
*/
|
|
66
|
+
static char *trim_whitespace(char *str) {
|
|
67
|
+
char *end;
|
|
68
|
+
|
|
69
|
+
/* Trim leading space */
|
|
70
|
+
while (isspace((unsigned char)*str)) str++;
|
|
71
|
+
|
|
72
|
+
if (*str == 0) return str;
|
|
73
|
+
|
|
74
|
+
/* Trim trailing space */
|
|
75
|
+
end = str + strlen(str) - 1;
|
|
76
|
+
while (end > str && isspace((unsigned char)*end)) end--;
|
|
77
|
+
|
|
78
|
+
end[1] = '\0';
|
|
79
|
+
return str;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Add a metadata item to the list
|
|
84
|
+
*/
|
|
85
|
+
static void add_metadata_item(apex_metadata_item **list, const char *key, const char *value) {
|
|
86
|
+
if (!key || !value) return; /* Don't add items with NULL key or value */
|
|
87
|
+
|
|
88
|
+
apex_metadata_item *item = malloc(sizeof(apex_metadata_item));
|
|
89
|
+
if (!item) return;
|
|
90
|
+
|
|
91
|
+
item->key = strdup(key);
|
|
92
|
+
item->value = strdup(value);
|
|
93
|
+
|
|
94
|
+
/* If strdup failed, free the item and don't add it */
|
|
95
|
+
if (!item->key || !item->value) {
|
|
96
|
+
free(item->key);
|
|
97
|
+
free(item->value);
|
|
98
|
+
free(item);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
item->next = *list;
|
|
103
|
+
*list = item;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
#ifdef APEX_HAVE_LIBYAML
|
|
107
|
+
/* Recursively convert libyaml node to flat metadata items */
|
|
108
|
+
static void yaml_node_to_flat_items(yaml_document_t *doc, yaml_node_t *node,
|
|
109
|
+
const char *prefix, apex_metadata_item **items) {
|
|
110
|
+
if (!node) return;
|
|
111
|
+
|
|
112
|
+
switch (node->type) {
|
|
113
|
+
case YAML_SCALAR_NODE: {
|
|
114
|
+
char *value = (char *)node->data.scalar.value;
|
|
115
|
+
size_t value_len = node->data.scalar.length;
|
|
116
|
+
char *value_str = malloc(value_len + 1);
|
|
117
|
+
if (value_str) {
|
|
118
|
+
memcpy(value_str, value, value_len);
|
|
119
|
+
value_str[value_len] = '\0';
|
|
120
|
+
if (prefix && *prefix) {
|
|
121
|
+
add_metadata_item(items, prefix, value_str);
|
|
122
|
+
}
|
|
123
|
+
free(value_str);
|
|
124
|
+
}
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
case YAML_SEQUENCE_NODE: {
|
|
128
|
+
/* For arrays, normalize to comma-separated string for backward compatibility with transforms */
|
|
129
|
+
/* Collect all scalar values and join with ", " */
|
|
130
|
+
size_t scalar_count = 0;
|
|
131
|
+
char **scalar_values = NULL;
|
|
132
|
+
size_t scalar_cap = 0;
|
|
133
|
+
|
|
134
|
+
yaml_node_item_t *item = node->data.sequence.items.start;
|
|
135
|
+
while (item < node->data.sequence.items.top) {
|
|
136
|
+
yaml_node_t *child = yaml_document_get_node(doc, *item);
|
|
137
|
+
if (child && child->type == YAML_SCALAR_NODE) {
|
|
138
|
+
if (scalar_count == scalar_cap) {
|
|
139
|
+
size_t new_cap = scalar_cap ? scalar_cap * 2 : 8;
|
|
140
|
+
char **tmp = realloc(scalar_values, new_cap * sizeof(char *));
|
|
141
|
+
if (!tmp) {
|
|
142
|
+
for (size_t i = 0; i < scalar_count; i++) free(scalar_values[i]);
|
|
143
|
+
free(scalar_values);
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
scalar_values = tmp;
|
|
147
|
+
scalar_cap = new_cap;
|
|
148
|
+
}
|
|
149
|
+
char *val = (char *)child->data.scalar.value;
|
|
150
|
+
size_t val_len = child->data.scalar.length;
|
|
151
|
+
scalar_values[scalar_count] = malloc(val_len + 1);
|
|
152
|
+
if (scalar_values[scalar_count]) {
|
|
153
|
+
memcpy(scalar_values[scalar_count], val, val_len);
|
|
154
|
+
scalar_values[scalar_count][val_len] = '\0';
|
|
155
|
+
scalar_count++;
|
|
156
|
+
}
|
|
157
|
+
} else if (child && child->type != YAML_SCALAR_NODE) {
|
|
158
|
+
/* Non-scalar in array - fall back to indexed keys */
|
|
159
|
+
for (size_t i = 0; i < scalar_count; i++) free(scalar_values[i]);
|
|
160
|
+
free(scalar_values);
|
|
161
|
+
scalar_count = 0;
|
|
162
|
+
scalar_values = NULL;
|
|
163
|
+
scalar_cap = 0;
|
|
164
|
+
|
|
165
|
+
/* Use indexed approach for complex arrays */
|
|
166
|
+
int idx = 0;
|
|
167
|
+
yaml_node_item_t *it = node->data.sequence.items.start;
|
|
168
|
+
while (it < node->data.sequence.items.top) {
|
|
169
|
+
char idx_key[512];
|
|
170
|
+
if (prefix && *prefix) {
|
|
171
|
+
snprintf(idx_key, sizeof(idx_key), "%s.%d", prefix, idx);
|
|
172
|
+
} else {
|
|
173
|
+
snprintf(idx_key, sizeof(idx_key), "%d", idx);
|
|
174
|
+
}
|
|
175
|
+
yaml_node_t *ch = yaml_document_get_node(doc, *it);
|
|
176
|
+
if (ch) {
|
|
177
|
+
yaml_node_to_flat_items(doc, ch, idx_key, items);
|
|
178
|
+
}
|
|
179
|
+
it++;
|
|
180
|
+
idx++;
|
|
181
|
+
}
|
|
182
|
+
break;
|
|
183
|
+
}
|
|
184
|
+
item++;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/* If we collected scalars, join them with ", " */
|
|
188
|
+
if (scalar_count > 0 && scalar_values) {
|
|
189
|
+
size_t total_len = 0;
|
|
190
|
+
for (size_t i = 0; i < scalar_count; i++) {
|
|
191
|
+
total_len += strlen(scalar_values[i]);
|
|
192
|
+
if (i < scalar_count - 1) total_len += 2; /* ", " */
|
|
193
|
+
}
|
|
194
|
+
char *joined = malloc(total_len + 1);
|
|
195
|
+
if (joined && prefix && *prefix) {
|
|
196
|
+
char *p = joined;
|
|
197
|
+
for (size_t i = 0; i < scalar_count; i++) {
|
|
198
|
+
size_t len = strlen(scalar_values[i]);
|
|
199
|
+
memcpy(p, scalar_values[i], len);
|
|
200
|
+
p += len;
|
|
201
|
+
if (i < scalar_count - 1) {
|
|
202
|
+
*p++ = ',';
|
|
203
|
+
*p++ = ' ';
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
*p = '\0';
|
|
207
|
+
add_metadata_item(items, prefix, joined);
|
|
208
|
+
free(joined);
|
|
209
|
+
}
|
|
210
|
+
for (size_t i = 0; i < scalar_count; i++) free(scalar_values[i]);
|
|
211
|
+
free(scalar_values);
|
|
212
|
+
}
|
|
213
|
+
break;
|
|
214
|
+
}
|
|
215
|
+
case YAML_MAPPING_NODE: {
|
|
216
|
+
yaml_node_pair_t *pair = node->data.mapping.pairs.start;
|
|
217
|
+
while (pair < node->data.mapping.pairs.top) {
|
|
218
|
+
yaml_node_t *key_node = yaml_document_get_node(doc, pair->key);
|
|
219
|
+
yaml_node_t *value_node = yaml_document_get_node(doc, pair->value);
|
|
220
|
+
|
|
221
|
+
if (key_node && key_node->type == YAML_SCALAR_NODE && value_node) {
|
|
222
|
+
char *key = (char *)key_node->data.scalar.value;
|
|
223
|
+
size_t key_len = key_node->data.scalar.length;
|
|
224
|
+
char *key_str = malloc(key_len + 1);
|
|
225
|
+
if (key_str) {
|
|
226
|
+
memcpy(key_str, key, key_len);
|
|
227
|
+
key_str[key_len] = '\0';
|
|
228
|
+
|
|
229
|
+
char full_key[512];
|
|
230
|
+
if (prefix && *prefix) {
|
|
231
|
+
snprintf(full_key, sizeof(full_key), "%s.%s", prefix, key_str);
|
|
232
|
+
} else {
|
|
233
|
+
strncpy(full_key, key_str, sizeof(full_key) - 1);
|
|
234
|
+
full_key[sizeof(full_key) - 1] = '\0';
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
yaml_node_to_flat_items(doc, value_node, full_key, items);
|
|
238
|
+
free(key_str);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
pair++;
|
|
242
|
+
}
|
|
243
|
+
break;
|
|
244
|
+
}
|
|
245
|
+
default:
|
|
246
|
+
break;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/* Parse YAML using libyaml - returns flat metadata items for backward compatibility */
|
|
251
|
+
static apex_metadata_item *parse_yaml_with_libyaml(const char *text, size_t text_len, size_t *consumed) {
|
|
252
|
+
yaml_parser_t parser;
|
|
253
|
+
yaml_document_t document;
|
|
254
|
+
apex_metadata_item *items = NULL;
|
|
255
|
+
|
|
256
|
+
/* Skip opening --- if present */
|
|
257
|
+
const char *yaml_start = text;
|
|
258
|
+
if (text_len >= 3 && strncmp(text, "---", 3) == 0) {
|
|
259
|
+
const char *newline = strchr(text + 3, '\n');
|
|
260
|
+
if (newline) {
|
|
261
|
+
yaml_start = newline + 1;
|
|
262
|
+
} else {
|
|
263
|
+
yaml_start = text + 3;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/* Find the end of YAML front matter (---) */
|
|
268
|
+
const char *end_marker = strstr(yaml_start, "\n---");
|
|
269
|
+
if (!end_marker) {
|
|
270
|
+
end_marker = strstr(yaml_start, "\n...");
|
|
271
|
+
}
|
|
272
|
+
if (!end_marker) {
|
|
273
|
+
/* Look for --- at start of line */
|
|
274
|
+
const char *p = yaml_start;
|
|
275
|
+
while ((p = strstr(p, "\n---")) != NULL) {
|
|
276
|
+
if (p[4] == '\n' || p[4] == '\0' || p[4] == '\r') {
|
|
277
|
+
end_marker = p;
|
|
278
|
+
break;
|
|
279
|
+
}
|
|
280
|
+
p += 4;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
size_t yaml_content_len = text_len - (yaml_start - text);
|
|
285
|
+
if (end_marker && end_marker > yaml_start) {
|
|
286
|
+
yaml_content_len = (size_t)(end_marker - yaml_start);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (!yaml_parser_initialize(&parser)) {
|
|
290
|
+
return NULL;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
yaml_parser_set_input_string(&parser, (const unsigned char *)yaml_start, yaml_content_len);
|
|
294
|
+
|
|
295
|
+
if (!yaml_parser_load(&parser, &document)) {
|
|
296
|
+
yaml_parser_delete(&parser);
|
|
297
|
+
return NULL;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
yaml_node_t *yaml_root = yaml_document_get_root_node(&document);
|
|
301
|
+
if (!yaml_root || yaml_root->type != YAML_MAPPING_NODE) {
|
|
302
|
+
yaml_document_delete(&document);
|
|
303
|
+
yaml_parser_delete(&parser);
|
|
304
|
+
return NULL;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/* Calculate consumed bytes */
|
|
308
|
+
if (end_marker) {
|
|
309
|
+
*consumed = (end_marker - text) + 4; /* Include \n--- */
|
|
310
|
+
} else {
|
|
311
|
+
*consumed = text_len;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/* Convert YAML mapping to flat metadata items */
|
|
315
|
+
yaml_node_to_flat_items(&document, yaml_root, NULL, &items);
|
|
316
|
+
|
|
317
|
+
yaml_document_delete(&document);
|
|
318
|
+
yaml_parser_delete(&parser);
|
|
319
|
+
return items;
|
|
320
|
+
}
|
|
321
|
+
#endif
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Parse YAML front matter
|
|
325
|
+
* Format: --- at start, key: value pairs, --- to close
|
|
326
|
+
* If libyaml is available, attempts to use it for full YAML support (arrays, nested structures)
|
|
327
|
+
* Falls back to simple line-by-line parser for backward compatibility
|
|
328
|
+
*/
|
|
329
|
+
static apex_metadata_item *parse_yaml_metadata(const char *text, size_t *consumed) {
|
|
330
|
+
apex_metadata_item *items = NULL;
|
|
331
|
+
#ifdef APEX_HAVE_LIBYAML
|
|
332
|
+
/* Try libyaml first for full YAML support */
|
|
333
|
+
size_t text_len = strlen(text);
|
|
334
|
+
items = parse_yaml_with_libyaml(text, text_len, consumed);
|
|
335
|
+
if (items) {
|
|
336
|
+
return items;
|
|
337
|
+
}
|
|
338
|
+
/* Fall through to simple parser if libyaml parsing failed */
|
|
339
|
+
#endif
|
|
340
|
+
const char *line_start = text;
|
|
341
|
+
const char *line_end;
|
|
342
|
+
|
|
343
|
+
/* Skip opening --- */
|
|
344
|
+
if (strncmp(text, "---", 3) != 0) return NULL;
|
|
345
|
+
line_start = strchr(text + 3, '\n');
|
|
346
|
+
if (!line_start) return NULL;
|
|
347
|
+
line_start++;
|
|
348
|
+
|
|
349
|
+
while ((line_end = strchr(line_start, '\n')) != NULL) {
|
|
350
|
+
size_t len = line_end - line_start;
|
|
351
|
+
char line[1024];
|
|
352
|
+
|
|
353
|
+
if (len >= sizeof(line)) len = sizeof(line) - 1;
|
|
354
|
+
memcpy(line, line_start, len);
|
|
355
|
+
line[len] = '\0';
|
|
356
|
+
|
|
357
|
+
/* Check for closing --- or ... */
|
|
358
|
+
char *trimmed = trim_whitespace(line);
|
|
359
|
+
if (strcmp(trimmed, "---") == 0 || strcmp(trimmed, "...") == 0) {
|
|
360
|
+
*consumed = (line_end + 1) - text;
|
|
361
|
+
return items;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/* Parse key: value */
|
|
365
|
+
char *colon = strchr(line, ':');
|
|
366
|
+
if (colon) {
|
|
367
|
+
*colon = '\0';
|
|
368
|
+
char *key = trim_whitespace(line);
|
|
369
|
+
char *value_ptr = trim_whitespace(colon + 1);
|
|
370
|
+
|
|
371
|
+
char final_value[1024] = {0};
|
|
372
|
+
strncpy(final_value, value_ptr, sizeof(final_value) - 1);
|
|
373
|
+
|
|
374
|
+
/* Strip quotes from value if present */
|
|
375
|
+
size_t value_len = strlen(final_value);
|
|
376
|
+
if (value_len >= 2 && ((final_value[0] == '"' && final_value[value_len - 1] == '"') ||
|
|
377
|
+
(final_value[0] == '\'' && final_value[value_len - 1] == '\''))) {
|
|
378
|
+
final_value[value_len - 1] = '\0';
|
|
379
|
+
memmove(final_value, final_value + 1, value_len - 1);
|
|
380
|
+
/* Re-trim after removing quotes */
|
|
381
|
+
trim_whitespace(final_value);
|
|
382
|
+
value_len = strlen(final_value);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
if (*key) {
|
|
386
|
+
add_metadata_item(&items, key, final_value);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
line_start = line_end + 1;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
return items;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Parse MultiMarkdown metadata
|
|
398
|
+
* Format: key: value pairs at start of document, blank line to end
|
|
399
|
+
*/
|
|
400
|
+
static apex_metadata_item *parse_mmd_metadata(const char *text, size_t *consumed) {
|
|
401
|
+
apex_metadata_item *items = NULL;
|
|
402
|
+
const char *line_start = text;
|
|
403
|
+
const char *line_end;
|
|
404
|
+
bool found_metadata = false;
|
|
405
|
+
|
|
406
|
+
while ((line_end = strchr(line_start, '\n')) != NULL) {
|
|
407
|
+
size_t len = line_end - line_start;
|
|
408
|
+
char line[1024];
|
|
409
|
+
|
|
410
|
+
if (len >= sizeof(line)) len = sizeof(line) - 1;
|
|
411
|
+
memcpy(line, line_start, len);
|
|
412
|
+
line[len] = '\0';
|
|
413
|
+
|
|
414
|
+
/* Check for blank line (end of metadata) */
|
|
415
|
+
char *trimmed = trim_whitespace(line);
|
|
416
|
+
if (*trimmed == '\0') {
|
|
417
|
+
if (found_metadata) {
|
|
418
|
+
*consumed = (line_end + 1) - text;
|
|
419
|
+
return items;
|
|
420
|
+
}
|
|
421
|
+
/* Skip leading blank lines */
|
|
422
|
+
line_start = line_end + 1;
|
|
423
|
+
continue;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/* Skip abbreviation definitions (*[abbr]: expansion or [>abbr]: expansion) */
|
|
427
|
+
if ((trimmed[0] == '*' && trimmed[1] == '[') ||
|
|
428
|
+
(trimmed[0] == '[' && trimmed[1] == '>')) {
|
|
429
|
+
/* This is an abbreviation, not metadata */
|
|
430
|
+
if (found_metadata) {
|
|
431
|
+
*consumed = line_start - text;
|
|
432
|
+
return items;
|
|
433
|
+
}
|
|
434
|
+
/* Haven't found metadata yet, skip this line */
|
|
435
|
+
line_start = line_end + 1;
|
|
436
|
+
continue;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/* Skip HTML comments (<!--...-->) */
|
|
440
|
+
if (strncmp(trimmed, "<!--", 4) == 0) {
|
|
441
|
+
/* This is an HTML comment, not metadata */
|
|
442
|
+
if (found_metadata) {
|
|
443
|
+
*consumed = line_start - text;
|
|
444
|
+
return items;
|
|
445
|
+
}
|
|
446
|
+
/* Haven't found metadata yet, skip this line */
|
|
447
|
+
line_start = line_end + 1;
|
|
448
|
+
continue;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/* Skip Kramdown markers ({::...}) */
|
|
452
|
+
if (strncmp(trimmed, "{::", 3) == 0) {
|
|
453
|
+
/* This is a Kramdown marker, not metadata */
|
|
454
|
+
if (found_metadata) {
|
|
455
|
+
*consumed = line_start - text;
|
|
456
|
+
return items;
|
|
457
|
+
}
|
|
458
|
+
/* Haven't found metadata yet, skip this line */
|
|
459
|
+
line_start = line_end + 1;
|
|
460
|
+
continue;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/* Skip Markdown headings (# or ##) */
|
|
464
|
+
if (trimmed[0] == '#') {
|
|
465
|
+
/* This is a Markdown heading, not metadata */
|
|
466
|
+
if (found_metadata) {
|
|
467
|
+
*consumed = line_start - text;
|
|
468
|
+
return items;
|
|
469
|
+
}
|
|
470
|
+
/* Haven't found metadata yet, skip this line */
|
|
471
|
+
line_start = line_end + 1;
|
|
472
|
+
continue;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
/* Skip IAL/ALD syntax ({: ...}) */
|
|
476
|
+
if (strncmp(trimmed, "{:", 2) == 0) {
|
|
477
|
+
/* This is IAL/ALD, not metadata */
|
|
478
|
+
if (found_metadata) {
|
|
479
|
+
*consumed = line_start - text;
|
|
480
|
+
return items;
|
|
481
|
+
}
|
|
482
|
+
/* Haven't found metadata yet, skip this line */
|
|
483
|
+
line_start = line_end + 1;
|
|
484
|
+
continue;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
/* Skip Leanpub index syntax ({i: term}) - colon would be parsed as metadata key:value */
|
|
488
|
+
if (strstr(trimmed, "{i:") != NULL) {
|
|
489
|
+
if (found_metadata) {
|
|
490
|
+
*consumed = line_start - text;
|
|
491
|
+
return items;
|
|
492
|
+
}
|
|
493
|
+
*consumed = 0;
|
|
494
|
+
return NULL;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
/* Skip TOC markers ({{TOC...}}) */
|
|
498
|
+
if (strncmp(trimmed, "{{TOC", 5) == 0) {
|
|
499
|
+
/* This is a TOC marker, not metadata */
|
|
500
|
+
if (found_metadata) {
|
|
501
|
+
*consumed = line_start - text;
|
|
502
|
+
return items;
|
|
503
|
+
}
|
|
504
|
+
/* Haven't found metadata yet, skip this line */
|
|
505
|
+
line_start = line_end + 1;
|
|
506
|
+
continue;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
/* Skip list markers (-, +, *, or numbered lists) */
|
|
510
|
+
/* Check if line starts with a list marker */
|
|
511
|
+
const char *list_check = trimmed;
|
|
512
|
+
if (*list_check == '-' || *list_check == '+' || *list_check == '*') {
|
|
513
|
+
/* Check if followed by space (markdown list syntax) */
|
|
514
|
+
if (list_check[1] == ' ' || list_check[1] == '\t') {
|
|
515
|
+
/* This is a list item, not metadata */
|
|
516
|
+
if (found_metadata) {
|
|
517
|
+
*consumed = line_start - text;
|
|
518
|
+
return items;
|
|
519
|
+
}
|
|
520
|
+
/* Haven't found metadata yet - this isn't metadata */
|
|
521
|
+
*consumed = 0;
|
|
522
|
+
return NULL;
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
/* Check for numbered lists (digit followed by . or ) and space) */
|
|
526
|
+
if (isdigit((unsigned char)*list_check)) {
|
|
527
|
+
const char *p = list_check + 1;
|
|
528
|
+
/* Skip digits */
|
|
529
|
+
while (isdigit((unsigned char)*p)) p++;
|
|
530
|
+
/* Check for . or ) followed by space */
|
|
531
|
+
if ((*p == '.' || *p == ')') && (p[1] == ' ' || p[1] == '\t')) {
|
|
532
|
+
/* This is a numbered list item, not metadata */
|
|
533
|
+
if (found_metadata) {
|
|
534
|
+
*consumed = line_start - text;
|
|
535
|
+
return items;
|
|
536
|
+
}
|
|
537
|
+
/* Haven't found metadata yet - this isn't metadata */
|
|
538
|
+
*consumed = 0;
|
|
539
|
+
return NULL;
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
/* Parse key: value */
|
|
544
|
+
char *colon = strchr(line, ':');
|
|
545
|
+
if (colon) {
|
|
546
|
+
/* Skip lines that look like Markdown links or images (e.g. [text](url) or  or ...[](#){: .class })
|
|
547
|
+
* so that "[{: .class }" is not parsed as metadata due to the ": " in "{: " */
|
|
548
|
+
if (strstr(line, "[!") != NULL ||
|
|
549
|
+
(strchr(line, '[') && strchr(line, ']') && strstr(line, "]("))) {
|
|
550
|
+
if (found_metadata) {
|
|
551
|
+
*consumed = line_start - text;
|
|
552
|
+
return items;
|
|
553
|
+
}
|
|
554
|
+
*consumed = 0;
|
|
555
|
+
return NULL;
|
|
556
|
+
}
|
|
557
|
+
/* Check if there's a protocol (http://, https://, mailto:) BEFORE the colon */
|
|
558
|
+
/* If so, this is likely a URL in the key, not metadata */
|
|
559
|
+
size_t key_len = (size_t)(colon - line);
|
|
560
|
+
if (key_len >= 7 && (
|
|
561
|
+
(key_len >= 7 && strncmp(line, "http://", 7) == 0) ||
|
|
562
|
+
(key_len >= 8 && strncmp(line, "https://", 8) == 0) ||
|
|
563
|
+
(key_len >= 7 && strncmp(line, "mailto:", 7) == 0) ||
|
|
564
|
+
strstr(line, "://") != NULL)) {
|
|
565
|
+
/* Protocol found before colon - this is a URL, not metadata */
|
|
566
|
+
if (found_metadata) {
|
|
567
|
+
*consumed = line_start - text;
|
|
568
|
+
return items;
|
|
569
|
+
}
|
|
570
|
+
*consumed = 0;
|
|
571
|
+
return NULL;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
/* Check if there's a < character BEFORE the colon (HTML/autolink in key) */
|
|
575
|
+
if (memchr(line, '<', key_len) != NULL) {
|
|
576
|
+
/* < found before colon - this is HTML/autolink, not metadata */
|
|
577
|
+
if (found_metadata) {
|
|
578
|
+
*consumed = line_start - text;
|
|
579
|
+
return items;
|
|
580
|
+
}
|
|
581
|
+
*consumed = 0;
|
|
582
|
+
return NULL;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
/* Check for space after colon (MMD requires "KEY: VALUE" format) */
|
|
586
|
+
char *after_colon = colon + 1;
|
|
587
|
+
if (*after_colon != ' ' && *after_colon != '\t') {
|
|
588
|
+
/* No space after colon - likely not metadata */
|
|
589
|
+
if (found_metadata) {
|
|
590
|
+
*consumed = line_start - text;
|
|
591
|
+
return items;
|
|
592
|
+
}
|
|
593
|
+
*consumed = 0;
|
|
594
|
+
return NULL;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
*colon = '\0';
|
|
598
|
+
char *key = trim_whitespace(line);
|
|
599
|
+
char *value = trim_whitespace(colon + 1);
|
|
600
|
+
|
|
601
|
+
if (*key && *value) {
|
|
602
|
+
add_metadata_item(&items, key, value);
|
|
603
|
+
found_metadata = true;
|
|
604
|
+
} else {
|
|
605
|
+
/* Line has colon but invalid key/value */
|
|
606
|
+
if (found_metadata) {
|
|
607
|
+
*consumed = line_start - text;
|
|
608
|
+
return items;
|
|
609
|
+
}
|
|
610
|
+
/* No metadata found yet - this isn't metadata */
|
|
611
|
+
*consumed = 0;
|
|
612
|
+
return NULL;
|
|
613
|
+
}
|
|
614
|
+
} else {
|
|
615
|
+
/* No colon - check if line contains URLs (bare URLs without angle brackets) */
|
|
616
|
+
if (strstr(trimmed, "http://") || strstr(trimmed, "https://") || strstr(trimmed, "mailto:")) {
|
|
617
|
+
/* This is a bare URL, not metadata */
|
|
618
|
+
if (found_metadata) {
|
|
619
|
+
*consumed = line_start - text;
|
|
620
|
+
return items;
|
|
621
|
+
}
|
|
622
|
+
*consumed = 0;
|
|
623
|
+
return NULL;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
/* Skip lines with Markdown links or images */
|
|
627
|
+
if (strchr(trimmed, '[') && strchr(trimmed, ']') && strchr(trimmed, '(')) {
|
|
628
|
+
/* Contains markdown link/image syntax - not metadata */
|
|
629
|
+
if (found_metadata) {
|
|
630
|
+
*consumed = line_start - text;
|
|
631
|
+
return items;
|
|
632
|
+
}
|
|
633
|
+
/* Haven't found metadata yet - this isn't metadata */
|
|
634
|
+
*consumed = 0;
|
|
635
|
+
return NULL;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
/* Non-metadata line found (no colon) */
|
|
639
|
+
if (found_metadata) {
|
|
640
|
+
*consumed = line_start - text;
|
|
641
|
+
return items;
|
|
642
|
+
}
|
|
643
|
+
/* No metadata found yet and hit non-metadata line - stop */
|
|
644
|
+
*consumed = 0;
|
|
645
|
+
return NULL;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
line_start = line_end + 1;
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
if (found_metadata) {
|
|
652
|
+
*consumed = strlen(text);
|
|
653
|
+
}
|
|
654
|
+
return items;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
/**
|
|
658
|
+
* Parse Pandoc title block metadata
|
|
659
|
+
* Format: % Title, % Author, % Date as first three lines
|
|
660
|
+
*/
|
|
661
|
+
static apex_metadata_item *parse_pandoc_metadata(const char *text, size_t *consumed) {
|
|
662
|
+
apex_metadata_item *items = NULL;
|
|
663
|
+
const char *keys[] = {"title", "author", "date"};
|
|
664
|
+
int key_index = 0;
|
|
665
|
+
const char *line_start = text;
|
|
666
|
+
const char *line_end;
|
|
667
|
+
|
|
668
|
+
while (key_index < 3 && (line_end = strchr(line_start, '\n')) != NULL) {
|
|
669
|
+
size_t len = line_end - line_start;
|
|
670
|
+
char line[1024];
|
|
671
|
+
|
|
672
|
+
if (len >= sizeof(line)) len = sizeof(line) - 1;
|
|
673
|
+
memcpy(line, line_start, len);
|
|
674
|
+
line[len] = '\0';
|
|
675
|
+
|
|
676
|
+
char *trimmed = trim_whitespace(line);
|
|
677
|
+
|
|
678
|
+
/* Must start with % */
|
|
679
|
+
if (*trimmed == '%') {
|
|
680
|
+
char *value = trim_whitespace(trimmed + 1);
|
|
681
|
+
if (*value) {
|
|
682
|
+
add_metadata_item(&items, keys[key_index], value);
|
|
683
|
+
}
|
|
684
|
+
key_index++;
|
|
685
|
+
} else {
|
|
686
|
+
/* Non-Pandoc line */
|
|
687
|
+
break;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
line_start = line_end + 1;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
if (key_index > 0) {
|
|
694
|
+
*consumed = line_start - text;
|
|
695
|
+
}
|
|
696
|
+
return items;
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
/**
|
|
700
|
+
* Detect and extract metadata from the start of document text
|
|
701
|
+
* This modifies the input by removing the metadata section
|
|
702
|
+
* Returns the extracted metadata
|
|
703
|
+
*/
|
|
704
|
+
apex_metadata_item *apex_extract_metadata(char **text_ptr) {
|
|
705
|
+
if (!text_ptr || !*text_ptr || !**text_ptr) return NULL;
|
|
706
|
+
|
|
707
|
+
char *text = *text_ptr;
|
|
708
|
+
size_t consumed = 0;
|
|
709
|
+
apex_metadata_item *items = NULL;
|
|
710
|
+
|
|
711
|
+
/* Try YAML first (most explicit) */
|
|
712
|
+
if (strncmp(text, "---", 3) == 0) {
|
|
713
|
+
items = parse_yaml_metadata(text, &consumed);
|
|
714
|
+
}
|
|
715
|
+
/* Try Pandoc */
|
|
716
|
+
else if (*text == '%') {
|
|
717
|
+
items = parse_pandoc_metadata(text, &consumed);
|
|
718
|
+
}
|
|
719
|
+
/* Try MMD (least specific) */
|
|
720
|
+
else {
|
|
721
|
+
items = parse_mmd_metadata(text, &consumed);
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
/* Remove metadata from text if found */
|
|
725
|
+
if (items && consumed > 0) {
|
|
726
|
+
/* Skip past the metadata */
|
|
727
|
+
*text_ptr = text + consumed;
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
return items;
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
/**
|
|
734
|
+
* Placeholder extension creation - for future full integration
|
|
735
|
+
* For now, metadata is handled via preprocessing
|
|
736
|
+
*/
|
|
737
|
+
cmark_syntax_extension *create_metadata_extension(void) {
|
|
738
|
+
/* Return NULL for now - we handle metadata via preprocessing */
|
|
739
|
+
/* In the future, this could create a proper block extension */
|
|
740
|
+
return NULL;
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
/**
|
|
744
|
+
* Get metadata from a document (stub for now)
|
|
745
|
+
*/
|
|
746
|
+
apex_metadata_item *apex_get_metadata(cmark_node *document) {
|
|
747
|
+
(void)document; /* Unused parameter */
|
|
748
|
+
/* For now, metadata must be extracted before parsing */
|
|
749
|
+
/* This would require storing metadata in the document's user_data */
|
|
750
|
+
return NULL;
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
/**
|
|
754
|
+
* Normalize metadata key by removing spaces and converting to lowercase
|
|
755
|
+
* This matches MultiMarkdown's behavior where "HTML Header Level" becomes "htmlheaderlevel"
|
|
756
|
+
*/
|
|
757
|
+
static char *normalize_metadata_key(const char *key) {
|
|
758
|
+
if (!key) return NULL;
|
|
759
|
+
|
|
760
|
+
size_t len = strlen(key);
|
|
761
|
+
char *normalized = malloc(len + 1);
|
|
762
|
+
if (!normalized) return NULL;
|
|
763
|
+
|
|
764
|
+
char *out = normalized;
|
|
765
|
+
for (const char *in = key; *in; in++) {
|
|
766
|
+
if (!isspace((unsigned char)*in)) {
|
|
767
|
+
*out++ = (char)tolower((unsigned char)*in);
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
*out = '\0';
|
|
771
|
+
return normalized;
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
/**
|
|
775
|
+
* Get a specific metadata value (case-insensitive, spaces ignored)
|
|
776
|
+
* Matches MultiMarkdown behavior where "HTML Header Level" matches "htmlheaderlevel"
|
|
777
|
+
*/
|
|
778
|
+
const char *apex_metadata_get(apex_metadata_item *metadata, const char *key) {
|
|
779
|
+
if (!key || !metadata) return NULL;
|
|
780
|
+
|
|
781
|
+
/* Validate key is a valid string (not just a non-NULL pointer) */
|
|
782
|
+
if (strlen(key) == 0) return NULL;
|
|
783
|
+
|
|
784
|
+
/* Normalize the search key */
|
|
785
|
+
char *normalized_key = normalize_metadata_key(key);
|
|
786
|
+
if (!normalized_key) return NULL;
|
|
787
|
+
|
|
788
|
+
/* Try exact case-insensitive match first (for backwards compatibility) */
|
|
789
|
+
for (apex_metadata_item *item = metadata; item != NULL; item = item->next) {
|
|
790
|
+
/* Validate item and its key before using */
|
|
791
|
+
if (!item) break;
|
|
792
|
+
if (!item->key) continue;
|
|
793
|
+
|
|
794
|
+
/* Additional safety: check if key is a valid string */
|
|
795
|
+
size_t key_len = strlen(item->key);
|
|
796
|
+
if (key_len == 0) continue;
|
|
797
|
+
|
|
798
|
+
if (strcasecmp(item->key, key) == 0) {
|
|
799
|
+
free(normalized_key);
|
|
800
|
+
return item->value;
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
/* Try normalized match (spaces removed, lowercase) */
|
|
805
|
+
for (apex_metadata_item *item = metadata; item != NULL; item = item->next) {
|
|
806
|
+
if (!item || !item->key) continue; /* Skip items with NULL keys */
|
|
807
|
+
|
|
808
|
+
/* Additional safety: check if key is a valid string */
|
|
809
|
+
size_t key_len = strlen(item->key);
|
|
810
|
+
if (key_len == 0) continue;
|
|
811
|
+
|
|
812
|
+
char *normalized_item_key = normalize_metadata_key(item->key);
|
|
813
|
+
if (normalized_item_key) {
|
|
814
|
+
bool match = (strcmp(normalized_item_key, normalized_key) == 0);
|
|
815
|
+
free(normalized_item_key);
|
|
816
|
+
if (match) {
|
|
817
|
+
free(normalized_key);
|
|
818
|
+
return item->value;
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
free(normalized_key);
|
|
824
|
+
return NULL;
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
/**
|
|
828
|
+
* Free transform chain
|
|
829
|
+
*/
|
|
830
|
+
static void free_transform_chain(apex_transform *transform) {
|
|
831
|
+
while (transform) {
|
|
832
|
+
apex_transform *next = transform->next;
|
|
833
|
+
free(transform->name);
|
|
834
|
+
free(transform->options);
|
|
835
|
+
free(transform);
|
|
836
|
+
transform = next;
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
/**
|
|
841
|
+
* Free string array
|
|
842
|
+
*/
|
|
843
|
+
static void free_string_array(apex_string_array *arr) {
|
|
844
|
+
if (arr) {
|
|
845
|
+
for (size_t i = 0; i < arr->count; i++) {
|
|
846
|
+
free(arr->items[i]);
|
|
847
|
+
}
|
|
848
|
+
free(arr->items);
|
|
849
|
+
free(arr);
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
/**
|
|
854
|
+
* Parse transform chain from string like "KEY:TRANSFORM1:TRANSFORM2(OPTIONS)"
|
|
855
|
+
* Returns the first transform in the chain, or NULL on error
|
|
856
|
+
* The key part is stored separately and returned via key_out
|
|
857
|
+
*/
|
|
858
|
+
static apex_transform *parse_transform_chain(const char *input, char **key_out) {
|
|
859
|
+
if (!input || !key_out) return NULL;
|
|
860
|
+
|
|
861
|
+
apex_transform *first = NULL;
|
|
862
|
+
apex_transform *current = NULL;
|
|
863
|
+
|
|
864
|
+
/* Find first colon to separate key from transforms */
|
|
865
|
+
const char *first_colon = strchr(input, ':');
|
|
866
|
+
if (!first_colon) {
|
|
867
|
+
/* No transforms, just a key */
|
|
868
|
+
*key_out = strdup(input);
|
|
869
|
+
return NULL;
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
/* Extract key */
|
|
873
|
+
size_t key_len = first_colon - input;
|
|
874
|
+
*key_out = malloc(key_len + 1);
|
|
875
|
+
if (!*key_out) return NULL;
|
|
876
|
+
memcpy(*key_out, input, key_len);
|
|
877
|
+
(*key_out)[key_len] = '\0';
|
|
878
|
+
|
|
879
|
+
/* Parse transforms */
|
|
880
|
+
const char *p = first_colon + 1;
|
|
881
|
+
|
|
882
|
+
while (*p) {
|
|
883
|
+
apex_transform *transform = calloc(1, sizeof(apex_transform));
|
|
884
|
+
if (!transform) {
|
|
885
|
+
free_transform_chain(first);
|
|
886
|
+
free(*key_out);
|
|
887
|
+
*key_out = NULL;
|
|
888
|
+
return NULL;
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
if (!first) {
|
|
892
|
+
first = transform;
|
|
893
|
+
}
|
|
894
|
+
if (current) {
|
|
895
|
+
current->next = transform;
|
|
896
|
+
}
|
|
897
|
+
current = transform;
|
|
898
|
+
|
|
899
|
+
/* Find end of transform name (colon or opening paren) */
|
|
900
|
+
const char *name_end = p;
|
|
901
|
+
while (*name_end && *name_end != ':' && *name_end != '(') {
|
|
902
|
+
name_end++;
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
size_t name_len = name_end - p;
|
|
906
|
+
transform->name = malloc(name_len + 1);
|
|
907
|
+
if (!transform->name) {
|
|
908
|
+
free_transform_chain(first);
|
|
909
|
+
free(*key_out);
|
|
910
|
+
*key_out = NULL;
|
|
911
|
+
return NULL;
|
|
912
|
+
}
|
|
913
|
+
memcpy(transform->name, p, name_len);
|
|
914
|
+
transform->name[name_len] = '\0';
|
|
915
|
+
|
|
916
|
+
p = name_end;
|
|
917
|
+
|
|
918
|
+
/* Check for options in parentheses */
|
|
919
|
+
if (*p == '(') {
|
|
920
|
+
p++; /* Skip opening paren */
|
|
921
|
+
const char *opt_start = p;
|
|
922
|
+
const char *opt_end = strchr(p, ')');
|
|
923
|
+
if (!opt_end) {
|
|
924
|
+
/* Malformed - missing closing paren */
|
|
925
|
+
free_transform_chain(first);
|
|
926
|
+
free(*key_out);
|
|
927
|
+
*key_out = NULL;
|
|
928
|
+
return NULL;
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
size_t opt_len = opt_end - opt_start;
|
|
932
|
+
transform->options = malloc(opt_len + 1);
|
|
933
|
+
if (!transform->options) {
|
|
934
|
+
free_transform_chain(first);
|
|
935
|
+
free(*key_out);
|
|
936
|
+
*key_out = NULL;
|
|
937
|
+
return NULL;
|
|
938
|
+
}
|
|
939
|
+
memcpy(transform->options, opt_start, opt_len);
|
|
940
|
+
transform->options[opt_len] = '\0';
|
|
941
|
+
|
|
942
|
+
p = opt_end + 1;
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
/* Skip colon separator if present */
|
|
946
|
+
if (*p == ':') {
|
|
947
|
+
p++;
|
|
948
|
+
} else if (*p != '\0') {
|
|
949
|
+
/* Unexpected character */
|
|
950
|
+
break;
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
return first;
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
/**
|
|
958
|
+
* Parse date string into struct tm
|
|
959
|
+
* Supports: YYYY-MM-DD HH:MM:SS, YYYY-MM-DD HH:MM, YYYY-MM-DD
|
|
960
|
+
*/
|
|
961
|
+
static bool parse_date(const char *date_str, struct tm *tm_out) {
|
|
962
|
+
if (!date_str || !tm_out) return false;
|
|
963
|
+
|
|
964
|
+
memset(tm_out, 0, sizeof(struct tm));
|
|
965
|
+
|
|
966
|
+
/* Try different formats */
|
|
967
|
+
#ifdef HAVE_STRPTIME
|
|
968
|
+
const char *formats[] = {
|
|
969
|
+
"%Y-%m-%d %H:%M:%S",
|
|
970
|
+
"%Y-%m-%d %H:%M",
|
|
971
|
+
"%Y-%m-%d"
|
|
972
|
+
};
|
|
973
|
+
|
|
974
|
+
for (size_t i = 0; i < sizeof(formats) / sizeof(formats[0]); i++) {
|
|
975
|
+
struct tm temp;
|
|
976
|
+
memset(&temp, 0, sizeof(temp));
|
|
977
|
+
|
|
978
|
+
char *end = strptime(date_str, formats[i], &temp);
|
|
979
|
+
if (end && *end == '\0') {
|
|
980
|
+
*tm_out = temp;
|
|
981
|
+
return true;
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
#else
|
|
985
|
+
/* Manual parsing fallback */
|
|
986
|
+
int year = 0, mon = 0, mday = 0, hour = 0, min = 0, sec = 0;
|
|
987
|
+
int items = sscanf(date_str, "%d-%d-%d %d:%d:%d", &year, &mon, &mday, &hour, &min, &sec);
|
|
988
|
+
if (items >= 3) {
|
|
989
|
+
/* At least got date part */
|
|
990
|
+
tm_out->tm_year = year - 1900; /* tm_year is years since 1900 */
|
|
991
|
+
tm_out->tm_mon = mon - 1; /* tm_mon is 0-based */
|
|
992
|
+
tm_out->tm_mday = mday;
|
|
993
|
+
if (items >= 4) tm_out->tm_hour = hour;
|
|
994
|
+
if (items >= 5) tm_out->tm_min = min;
|
|
995
|
+
if (items >= 6) tm_out->tm_sec = sec;
|
|
996
|
+
return true;
|
|
997
|
+
}
|
|
998
|
+
#endif
|
|
999
|
+
|
|
1000
|
+
return false;
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
/* Simple string split fallback (for when regex fails) */
|
|
1004
|
+
static apex_string_array *split_string_simple(const char *str, const char *delimiter) {
|
|
1005
|
+
if (!str) return NULL;
|
|
1006
|
+
|
|
1007
|
+
apex_string_array *arr = calloc(1, sizeof(apex_string_array));
|
|
1008
|
+
if (!arr) return NULL;
|
|
1009
|
+
|
|
1010
|
+
arr->capacity = 8;
|
|
1011
|
+
arr->items = malloc(arr->capacity * sizeof(char*));
|
|
1012
|
+
if (!arr->items) {
|
|
1013
|
+
free(arr);
|
|
1014
|
+
return NULL;
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
if (!delimiter || delimiter[0] == '\0') {
|
|
1018
|
+
delimiter = ","; /* Default to comma */
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
char *str_copy = strdup(str);
|
|
1022
|
+
if (!str_copy) {
|
|
1023
|
+
free(arr->items);
|
|
1024
|
+
free(arr);
|
|
1025
|
+
return NULL;
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
char *token = strtok(str_copy, delimiter);
|
|
1029
|
+
while (token) {
|
|
1030
|
+
/* Trim whitespace from token */
|
|
1031
|
+
char *start = token;
|
|
1032
|
+
while (*start && isspace((unsigned char)*start)) start++;
|
|
1033
|
+
char *end = start + strlen(start);
|
|
1034
|
+
while (end > start && isspace((unsigned char)*(end-1))) end--;
|
|
1035
|
+
*end = '\0';
|
|
1036
|
+
|
|
1037
|
+
if (arr->count >= arr->capacity) {
|
|
1038
|
+
arr->capacity *= 2;
|
|
1039
|
+
arr->items = realloc(arr->items, arr->capacity * sizeof(char*));
|
|
1040
|
+
if (!arr->items) {
|
|
1041
|
+
free(str_copy);
|
|
1042
|
+
free_string_array(arr);
|
|
1043
|
+
return NULL;
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
arr->items[arr->count] = strdup(start);
|
|
1048
|
+
if (!arr->items[arr->count]) {
|
|
1049
|
+
free(str_copy);
|
|
1050
|
+
free_string_array(arr);
|
|
1051
|
+
return NULL;
|
|
1052
|
+
}
|
|
1053
|
+
arr->count++;
|
|
1054
|
+
|
|
1055
|
+
token = strtok(NULL, delimiter);
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
free(str_copy);
|
|
1059
|
+
return arr;
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
/**
|
|
1063
|
+
* Create string array from string using regex delimiter
|
|
1064
|
+
*/
|
|
1065
|
+
static apex_string_array *split_string(const char *str, const char *delimiter_pattern) {
|
|
1066
|
+
if (!str) return NULL;
|
|
1067
|
+
|
|
1068
|
+
apex_string_array *arr = calloc(1, sizeof(apex_string_array));
|
|
1069
|
+
if (!arr) return NULL;
|
|
1070
|
+
|
|
1071
|
+
arr->capacity = 8;
|
|
1072
|
+
arr->items = malloc(arr->capacity * sizeof(char*));
|
|
1073
|
+
if (!arr->items) {
|
|
1074
|
+
free(arr);
|
|
1075
|
+
return NULL;
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
const char *pattern = delimiter_pattern && delimiter_pattern[0] ? delimiter_pattern : "\\s+";
|
|
1079
|
+
|
|
1080
|
+
regex_t regex;
|
|
1081
|
+
int ret = regcomp(®ex, pattern, REG_EXTENDED);
|
|
1082
|
+
if (ret != 0) {
|
|
1083
|
+
/* Regex compilation failed, fall back to simple string split */
|
|
1084
|
+
free(arr->items);
|
|
1085
|
+
free(arr);
|
|
1086
|
+
const char *fallback = delimiter_pattern && delimiter_pattern[0] ? delimiter_pattern : ",";
|
|
1087
|
+
return split_string_simple(str, fallback);
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
const char *search_pos = str;
|
|
1091
|
+
regmatch_t matches[1];
|
|
1092
|
+
|
|
1093
|
+
while (regexec(®ex, search_pos, 1, matches, 0) == 0) {
|
|
1094
|
+
/* Extract token before match */
|
|
1095
|
+
size_t token_len = (size_t)matches[0].rm_so;
|
|
1096
|
+
if (token_len > 0) {
|
|
1097
|
+
if (arr->count >= arr->capacity) {
|
|
1098
|
+
arr->capacity *= 2;
|
|
1099
|
+
arr->items = realloc(arr->items, arr->capacity * sizeof(char*));
|
|
1100
|
+
if (!arr->items) {
|
|
1101
|
+
regfree(®ex);
|
|
1102
|
+
free_string_array(arr);
|
|
1103
|
+
return NULL;
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
char *token = malloc(token_len + 1);
|
|
1108
|
+
if (!token) {
|
|
1109
|
+
regfree(®ex);
|
|
1110
|
+
free_string_array(arr);
|
|
1111
|
+
return NULL;
|
|
1112
|
+
}
|
|
1113
|
+
memcpy(token, search_pos, token_len);
|
|
1114
|
+
token[token_len] = '\0';
|
|
1115
|
+
|
|
1116
|
+
/* Trim whitespace from token */
|
|
1117
|
+
char *start = token;
|
|
1118
|
+
while (*start && isspace((unsigned char)*start)) start++;
|
|
1119
|
+
char *end = start + strlen(start);
|
|
1120
|
+
while (end > start && isspace((unsigned char)*(end-1))) end--;
|
|
1121
|
+
*end = '\0';
|
|
1122
|
+
|
|
1123
|
+
if (*start) { /* Only add non-empty tokens */
|
|
1124
|
+
arr->items[arr->count] = strdup(start);
|
|
1125
|
+
if (!arr->items[arr->count]) {
|
|
1126
|
+
free(token);
|
|
1127
|
+
regfree(®ex);
|
|
1128
|
+
free_string_array(arr);
|
|
1129
|
+
return NULL;
|
|
1130
|
+
}
|
|
1131
|
+
arr->count++;
|
|
1132
|
+
}
|
|
1133
|
+
free(token);
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
search_pos += matches[0].rm_eo;
|
|
1137
|
+
if (*search_pos == '\0') break;
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
/* Handle remaining text after last match */
|
|
1141
|
+
if (*search_pos) {
|
|
1142
|
+
size_t remaining_len = strlen(search_pos);
|
|
1143
|
+
if (remaining_len > 0) {
|
|
1144
|
+
if (arr->count >= arr->capacity) {
|
|
1145
|
+
arr->capacity *= 2;
|
|
1146
|
+
arr->items = realloc(arr->items, arr->capacity * sizeof(char*));
|
|
1147
|
+
if (!arr->items) {
|
|
1148
|
+
regfree(®ex);
|
|
1149
|
+
free_string_array(arr);
|
|
1150
|
+
return NULL;
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
char *token = strdup(search_pos);
|
|
1155
|
+
if (token) {
|
|
1156
|
+
/* Trim whitespace */
|
|
1157
|
+
char *start = token;
|
|
1158
|
+
while (*start && isspace((unsigned char)*start)) start++;
|
|
1159
|
+
char *end = start + strlen(start);
|
|
1160
|
+
while (end > start && isspace((unsigned char)*(end-1))) end--;
|
|
1161
|
+
*end = '\0';
|
|
1162
|
+
|
|
1163
|
+
if (*start) {
|
|
1164
|
+
arr->items[arr->count] = strdup(start);
|
|
1165
|
+
if (arr->items[arr->count]) {
|
|
1166
|
+
arr->count++;
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
free(token);
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
regfree(®ex);
|
|
1175
|
+
|
|
1176
|
+
/* If no matches found, return array with single element (original string) */
|
|
1177
|
+
if (arr->count == 0) {
|
|
1178
|
+
arr->items[0] = strdup(str);
|
|
1179
|
+
if (arr->items[0]) {
|
|
1180
|
+
arr->count = 1;
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
return arr;
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
/**
|
|
1188
|
+
* Apply a single transform to a value
|
|
1189
|
+
* Returns newly allocated string, or NULL on error
|
|
1190
|
+
* If is_array is true, value is treated as an array (apex_string_array*)
|
|
1191
|
+
*/
|
|
1192
|
+
static char *apply_transform(const char *transform_name, const char *options,
|
|
1193
|
+
const char *value, apex_string_array **array_ptr, bool *is_array) {
|
|
1194
|
+
if (!transform_name || !value) return NULL;
|
|
1195
|
+
|
|
1196
|
+
/* Handle array transforms */
|
|
1197
|
+
if (strcmp(transform_name, "split") == 0) {
|
|
1198
|
+
const char *delim = options && options[0] ? options : " ";
|
|
1199
|
+
apex_string_array *arr = split_string(value, delim);
|
|
1200
|
+
if (!arr) return NULL;
|
|
1201
|
+
if (*array_ptr) free_string_array(*array_ptr);
|
|
1202
|
+
*array_ptr = arr;
|
|
1203
|
+
*is_array = true;
|
|
1204
|
+
/* Return first element as representation */
|
|
1205
|
+
return arr->count > 0 ? strdup(arr->items[0]) : strdup("");
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
if (strcmp(transform_name, "join") == 0) {
|
|
1209
|
+
if (!*is_array || !*array_ptr) {
|
|
1210
|
+
/* Not an array yet, try to split on commas */
|
|
1211
|
+
apex_string_array *arr = split_string(value, ",");
|
|
1212
|
+
if (!arr) return NULL;
|
|
1213
|
+
if (*array_ptr) free_string_array(*array_ptr);
|
|
1214
|
+
*array_ptr = arr;
|
|
1215
|
+
*is_array = true;
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
const char *delim = options && options[0] ? options : ", ";
|
|
1219
|
+
apex_string_array *arr = *array_ptr;
|
|
1220
|
+
|
|
1221
|
+
if (arr->count == 0) return strdup("");
|
|
1222
|
+
if (arr->count == 1) return strdup(arr->items[0]);
|
|
1223
|
+
|
|
1224
|
+
/* Calculate total length */
|
|
1225
|
+
size_t total_len = 0;
|
|
1226
|
+
size_t delim_len = strlen(delim);
|
|
1227
|
+
for (size_t i = 0; i < arr->count; i++) {
|
|
1228
|
+
total_len += strlen(arr->items[i]);
|
|
1229
|
+
if (i < arr->count - 1) total_len += delim_len;
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
char *result = malloc(total_len + 1);
|
|
1233
|
+
if (!result) return NULL;
|
|
1234
|
+
|
|
1235
|
+
char *p = result;
|
|
1236
|
+
for (size_t i = 0; i < arr->count; i++) {
|
|
1237
|
+
size_t len = strlen(arr->items[i]);
|
|
1238
|
+
memcpy(p, arr->items[i], len);
|
|
1239
|
+
p += len;
|
|
1240
|
+
if (i < arr->count - 1) {
|
|
1241
|
+
memcpy(p, delim, delim_len);
|
|
1242
|
+
p += delim_len;
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
*p = '\0';
|
|
1246
|
+
|
|
1247
|
+
/* Join converts array back to string */
|
|
1248
|
+
*is_array = false;
|
|
1249
|
+
return result;
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
if (strcmp(transform_name, "first") == 0) {
|
|
1253
|
+
if (!*is_array || !*array_ptr) {
|
|
1254
|
+
/* Not an array yet, try to split on commas */
|
|
1255
|
+
apex_string_array *arr = split_string(value, ",");
|
|
1256
|
+
if (!arr) return NULL;
|
|
1257
|
+
if (*array_ptr) free_string_array(*array_ptr);
|
|
1258
|
+
*array_ptr = arr;
|
|
1259
|
+
*is_array = true;
|
|
1260
|
+
}
|
|
1261
|
+
apex_string_array *arr = *array_ptr;
|
|
1262
|
+
if (arr->count == 0) return strdup("");
|
|
1263
|
+
return strdup(arr->items[0]);
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
if (strcmp(transform_name, "last") == 0) {
|
|
1267
|
+
if (!*is_array || !*array_ptr) {
|
|
1268
|
+
/* Not an array yet, try to split on commas */
|
|
1269
|
+
apex_string_array *arr = split_string(value, ",");
|
|
1270
|
+
if (!arr) return NULL;
|
|
1271
|
+
if (*array_ptr) free_string_array(*array_ptr);
|
|
1272
|
+
*array_ptr = arr;
|
|
1273
|
+
*is_array = true;
|
|
1274
|
+
}
|
|
1275
|
+
apex_string_array *arr = *array_ptr;
|
|
1276
|
+
if (arr->count == 0) return strdup("");
|
|
1277
|
+
return strdup(arr->items[arr->count - 1]);
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
if (strcmp(transform_name, "slice") == 0) {
|
|
1281
|
+
if (!*is_array || !*array_ptr) {
|
|
1282
|
+
/* Not an array yet - split string into individual characters */
|
|
1283
|
+
size_t value_len = strlen(value);
|
|
1284
|
+
apex_string_array *arr = calloc(1, sizeof(apex_string_array));
|
|
1285
|
+
if (!arr) return NULL;
|
|
1286
|
+
|
|
1287
|
+
if (value_len == 0) {
|
|
1288
|
+
/* Empty string - create empty array */
|
|
1289
|
+
arr->capacity = 1;
|
|
1290
|
+
arr->items = malloc(sizeof(char*));
|
|
1291
|
+
if (!arr->items) {
|
|
1292
|
+
free(arr);
|
|
1293
|
+
return NULL;
|
|
1294
|
+
}
|
|
1295
|
+
arr->count = 0;
|
|
1296
|
+
} else {
|
|
1297
|
+
arr->capacity = value_len;
|
|
1298
|
+
arr->items = malloc(arr->capacity * sizeof(char*));
|
|
1299
|
+
if (!arr->items) {
|
|
1300
|
+
free(arr);
|
|
1301
|
+
return NULL;
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
for (size_t i = 0; i < value_len; i++) {
|
|
1305
|
+
arr->items[i] = malloc(2);
|
|
1306
|
+
if (!arr->items[i]) {
|
|
1307
|
+
free_string_array(arr);
|
|
1308
|
+
return NULL;
|
|
1309
|
+
}
|
|
1310
|
+
arr->items[i][0] = value[i];
|
|
1311
|
+
arr->items[i][1] = '\0';
|
|
1312
|
+
arr->count++;
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1316
|
+
if (*array_ptr) free_string_array(*array_ptr);
|
|
1317
|
+
*array_ptr = arr;
|
|
1318
|
+
*is_array = true;
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
if (!options) return strdup(value);
|
|
1322
|
+
|
|
1323
|
+
long start = 0, len = -1;
|
|
1324
|
+
if (sscanf(options, "%ld,%ld", &start, &len) >= 1) {
|
|
1325
|
+
apex_string_array *arr = *array_ptr;
|
|
1326
|
+
if (start < 0) start = 0;
|
|
1327
|
+
if (start >= (long)arr->count) return strdup("");
|
|
1328
|
+
if (len < 0) len = (long)arr->count - start;
|
|
1329
|
+
if (len > 0 && (size_t)(start + len) > arr->count) len = (long)arr->count - start;
|
|
1330
|
+
|
|
1331
|
+
/* Create new array with slice */
|
|
1332
|
+
apex_string_array *slice = calloc(1, sizeof(apex_string_array));
|
|
1333
|
+
if (!slice) return NULL;
|
|
1334
|
+
slice->capacity = len;
|
|
1335
|
+
slice->items = malloc(len * sizeof(char*));
|
|
1336
|
+
if (!slice->items) {
|
|
1337
|
+
free(slice);
|
|
1338
|
+
return NULL;
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1341
|
+
for (long i = 0; i < len; i++) {
|
|
1342
|
+
slice->items[i] = strdup(arr->items[start + i]);
|
|
1343
|
+
if (!slice->items[i]) {
|
|
1344
|
+
free_string_array(slice);
|
|
1345
|
+
return NULL;
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
slice->count = len;
|
|
1349
|
+
|
|
1350
|
+
/* Replace old array */
|
|
1351
|
+
if (*array_ptr) free_string_array(*array_ptr);
|
|
1352
|
+
*array_ptr = slice;
|
|
1353
|
+
*is_array = true;
|
|
1354
|
+
|
|
1355
|
+
/* Return joined representation with no separator */
|
|
1356
|
+
size_t total_len = 0;
|
|
1357
|
+
for (size_t i = 0; i < slice->count; i++) {
|
|
1358
|
+
total_len += strlen(slice->items[i]);
|
|
1359
|
+
}
|
|
1360
|
+
char *result = malloc(total_len + 1);
|
|
1361
|
+
if (!result) return NULL;
|
|
1362
|
+
char *p = result;
|
|
1363
|
+
for (size_t i = 0; i < slice->count; i++) {
|
|
1364
|
+
size_t item_len = strlen(slice->items[i]);
|
|
1365
|
+
memcpy(p, slice->items[i], item_len);
|
|
1366
|
+
p += item_len;
|
|
1367
|
+
}
|
|
1368
|
+
*p = '\0';
|
|
1369
|
+
return result;
|
|
1370
|
+
}
|
|
1371
|
+
return strdup(value);
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
/* String transforms */
|
|
1375
|
+
if (*is_array) {
|
|
1376
|
+
/* Convert array to string first */
|
|
1377
|
+
apex_string_array *arr = *array_ptr;
|
|
1378
|
+
if (arr->count == 0) value = "";
|
|
1379
|
+
else if (arr->count == 1) value = arr->items[0];
|
|
1380
|
+
else {
|
|
1381
|
+
/* Join array */
|
|
1382
|
+
size_t total_len = 0;
|
|
1383
|
+
for (size_t i = 0; i < arr->count; i++) {
|
|
1384
|
+
total_len += strlen(arr->items[i]);
|
|
1385
|
+
if (i < arr->count - 1) total_len += 2;
|
|
1386
|
+
}
|
|
1387
|
+
char *joined = malloc(total_len + 1);
|
|
1388
|
+
if (!joined) return NULL;
|
|
1389
|
+
char *p = joined;
|
|
1390
|
+
for (size_t i = 0; i < arr->count; i++) {
|
|
1391
|
+
size_t len = strlen(arr->items[i]);
|
|
1392
|
+
memcpy(p, arr->items[i], len);
|
|
1393
|
+
p += len;
|
|
1394
|
+
if (i < arr->count - 1) {
|
|
1395
|
+
*p++ = ',';
|
|
1396
|
+
*p++ = ' ';
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1399
|
+
*p = '\0';
|
|
1400
|
+
value = joined;
|
|
1401
|
+
*is_array = false;
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1404
|
+
|
|
1405
|
+
if (strcmp(transform_name, "upper") == 0) {
|
|
1406
|
+
char *result = strdup(value);
|
|
1407
|
+
if (!result) return NULL;
|
|
1408
|
+
for (char *p = result; *p; p++) {
|
|
1409
|
+
*p = (char)toupper((unsigned char)*p);
|
|
1410
|
+
}
|
|
1411
|
+
return result;
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1414
|
+
if (strcmp(transform_name, "lower") == 0) {
|
|
1415
|
+
char *result = strdup(value);
|
|
1416
|
+
if (!result) return NULL;
|
|
1417
|
+
for (char *p = result; *p; p++) {
|
|
1418
|
+
*p = (char)tolower((unsigned char)*p);
|
|
1419
|
+
}
|
|
1420
|
+
return result;
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1423
|
+
if (strcmp(transform_name, "trim") == 0) {
|
|
1424
|
+
const char *start = value;
|
|
1425
|
+
while (*start && isspace((unsigned char)*start)) start++;
|
|
1426
|
+
if (*start == '\0') return strdup("");
|
|
1427
|
+
const char *end = start + strlen(start) - 1;
|
|
1428
|
+
while (end > start && isspace((unsigned char)*end)) end--;
|
|
1429
|
+
size_t len = end - start + 1;
|
|
1430
|
+
char *result = malloc(len + 1);
|
|
1431
|
+
if (!result) return NULL;
|
|
1432
|
+
memcpy(result, start, len);
|
|
1433
|
+
result[len] = '\0';
|
|
1434
|
+
return result;
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1437
|
+
if (strcmp(transform_name, "title") == 0) {
|
|
1438
|
+
char *result = strdup(value);
|
|
1439
|
+
if (!result) return NULL;
|
|
1440
|
+
bool prev_space = true;
|
|
1441
|
+
for (char *p = result; *p; p++) {
|
|
1442
|
+
if (isspace((unsigned char)*p)) {
|
|
1443
|
+
prev_space = true;
|
|
1444
|
+
} else {
|
|
1445
|
+
if (prev_space) {
|
|
1446
|
+
*p = (char)toupper((unsigned char)*p);
|
|
1447
|
+
} else {
|
|
1448
|
+
*p = (char)tolower((unsigned char)*p);
|
|
1449
|
+
}
|
|
1450
|
+
prev_space = false;
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
return result;
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1456
|
+
if (strcmp(transform_name, "strftime") == 0) {
|
|
1457
|
+
if (!options) return strdup(value);
|
|
1458
|
+
|
|
1459
|
+
struct tm tm;
|
|
1460
|
+
if (!parse_date(value, &tm)) {
|
|
1461
|
+
/* Date parsing failed, return original */
|
|
1462
|
+
return strdup(value);
|
|
1463
|
+
}
|
|
1464
|
+
|
|
1465
|
+
/* Use strftime to format */
|
|
1466
|
+
char buffer[256];
|
|
1467
|
+
size_t result_len = strftime(buffer, sizeof(buffer), options, &tm);
|
|
1468
|
+
if (result_len == 0) {
|
|
1469
|
+
/* Format failed, return original */
|
|
1470
|
+
return strdup(value);
|
|
1471
|
+
}
|
|
1472
|
+
return strdup(buffer);
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
if (strcmp(transform_name, "capitalize") == 0) {
|
|
1476
|
+
char *result = strdup(value);
|
|
1477
|
+
if (!result) return NULL;
|
|
1478
|
+
if (*result) {
|
|
1479
|
+
*result = (char)toupper((unsigned char)*result);
|
|
1480
|
+
}
|
|
1481
|
+
return result;
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
if (strcmp(transform_name, "slug") == 0 || strcmp(transform_name, "slugify") == 0) {
|
|
1485
|
+
char *result = malloc(strlen(value) * 2 + 1); /* Worst case: every char becomes hyphen */
|
|
1486
|
+
if (!result) return NULL;
|
|
1487
|
+
char *out = result;
|
|
1488
|
+
bool prev_was_hyphen = false;
|
|
1489
|
+
|
|
1490
|
+
for (const char *p = value; *p; p++) {
|
|
1491
|
+
unsigned char c = (unsigned char)*p;
|
|
1492
|
+
if (isalnum(c)) {
|
|
1493
|
+
*out++ = (char)tolower(c);
|
|
1494
|
+
prev_was_hyphen = false;
|
|
1495
|
+
} else if (isspace(c) || c == '_') {
|
|
1496
|
+
if (!prev_was_hyphen) {
|
|
1497
|
+
*out++ = '-';
|
|
1498
|
+
prev_was_hyphen = true;
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1501
|
+
/* Skip other special characters */
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1504
|
+
/* Remove trailing hyphens */
|
|
1505
|
+
while (out > result && out[-1] == '-') {
|
|
1506
|
+
out--;
|
|
1507
|
+
}
|
|
1508
|
+
|
|
1509
|
+
*out = '\0';
|
|
1510
|
+
return result;
|
|
1511
|
+
}
|
|
1512
|
+
|
|
1513
|
+
if (strcmp(transform_name, "replace") == 0) {
|
|
1514
|
+
if (!options) return strdup(value);
|
|
1515
|
+
|
|
1516
|
+
/* Parse options: OLD,NEW or regex:OLD,NEW */
|
|
1517
|
+
char *options_copy = strdup(options);
|
|
1518
|
+
if (!options_copy) return strdup(value);
|
|
1519
|
+
|
|
1520
|
+
char *old_str = options_copy;
|
|
1521
|
+
char *new_str = strchr(options_copy, ',');
|
|
1522
|
+
bool use_regex = false;
|
|
1523
|
+
|
|
1524
|
+
/* Check if it's regex: prefix */
|
|
1525
|
+
if (strncmp(old_str, "regex:", 6) == 0) {
|
|
1526
|
+
use_regex = true;
|
|
1527
|
+
}
|
|
1528
|
+
|
|
1529
|
+
if (!new_str) {
|
|
1530
|
+
/* No comma found, treat entire options as search string */
|
|
1531
|
+
free(options_copy);
|
|
1532
|
+
return strdup(value);
|
|
1533
|
+
}
|
|
1534
|
+
|
|
1535
|
+
/* Extract old_str and new_str before modifying options_copy */
|
|
1536
|
+
/* Calculate length before any pointer modifications */
|
|
1537
|
+
size_t old_str_start_len = new_str - old_str;
|
|
1538
|
+
if (old_str_start_len == 0) {
|
|
1539
|
+
free(options_copy);
|
|
1540
|
+
return strdup(value);
|
|
1541
|
+
}
|
|
1542
|
+
char *old_pattern = malloc(old_str_start_len + 1);
|
|
1543
|
+
if (!old_pattern) {
|
|
1544
|
+
free(options_copy);
|
|
1545
|
+
return strdup(value);
|
|
1546
|
+
}
|
|
1547
|
+
memcpy(old_pattern, old_str, old_str_start_len);
|
|
1548
|
+
old_pattern[old_str_start_len] = '\0';
|
|
1549
|
+
|
|
1550
|
+
/* If regex, remove the "regex:" prefix from the pattern */
|
|
1551
|
+
if (use_regex) {
|
|
1552
|
+
/* We already detected regex: prefix, so remove it from old_pattern */
|
|
1553
|
+
if (old_str_start_len > 6 && strncmp(old_pattern, "regex:", 6) == 0) {
|
|
1554
|
+
size_t pattern_len = old_str_start_len - 6;
|
|
1555
|
+
memmove(old_pattern, old_pattern + 6, pattern_len);
|
|
1556
|
+
old_pattern[pattern_len] = '\0';
|
|
1557
|
+
}
|
|
1558
|
+
}
|
|
1559
|
+
|
|
1560
|
+
*new_str = '\0';
|
|
1561
|
+
new_str++;
|
|
1562
|
+
|
|
1563
|
+
char *result = NULL;
|
|
1564
|
+
|
|
1565
|
+
if (use_regex) {
|
|
1566
|
+
/* Use regex replacement */
|
|
1567
|
+
regex_t regex;
|
|
1568
|
+
int ret = regcomp(®ex, old_pattern, REG_EXTENDED);
|
|
1569
|
+
if (ret == 0) {
|
|
1570
|
+
regmatch_t matches[1];
|
|
1571
|
+
/* Count matches first to estimate size */
|
|
1572
|
+
size_t count = 0;
|
|
1573
|
+
const char *search_pos = value;
|
|
1574
|
+
while (regexec(®ex, search_pos, 1, matches, 0) == 0) {
|
|
1575
|
+
count++;
|
|
1576
|
+
search_pos += matches[0].rm_eo;
|
|
1577
|
+
if (*search_pos == '\0') break;
|
|
1578
|
+
}
|
|
1579
|
+
|
|
1580
|
+
if (count > 0) {
|
|
1581
|
+
/* Build result with replacements */
|
|
1582
|
+
size_t result_cap = strlen(value) + count * (strlen(new_str) + 10);
|
|
1583
|
+
result = malloc(result_cap);
|
|
1584
|
+
if (result) {
|
|
1585
|
+
char *out = result;
|
|
1586
|
+
const char *src = value;
|
|
1587
|
+
|
|
1588
|
+
search_pos = src;
|
|
1589
|
+
while (regexec(®ex, search_pos, 1, matches, 0) == 0) {
|
|
1590
|
+
/* Copy text before match */
|
|
1591
|
+
size_t before_len = matches[0].rm_so;
|
|
1592
|
+
memcpy(out, search_pos, before_len);
|
|
1593
|
+
out += before_len;
|
|
1594
|
+
|
|
1595
|
+
/* Copy replacement */
|
|
1596
|
+
size_t repl_len = strlen(new_str);
|
|
1597
|
+
memcpy(out, new_str, repl_len);
|
|
1598
|
+
out += repl_len;
|
|
1599
|
+
|
|
1600
|
+
search_pos += matches[0].rm_eo;
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1603
|
+
/* Copy remaining text */
|
|
1604
|
+
strcpy(out, search_pos);
|
|
1605
|
+
} else {
|
|
1606
|
+
/* Malloc failed, return original */
|
|
1607
|
+
result = strdup(value);
|
|
1608
|
+
}
|
|
1609
|
+
} else {
|
|
1610
|
+
/* No matches, return original */
|
|
1611
|
+
result = strdup(value);
|
|
1612
|
+
}
|
|
1613
|
+
|
|
1614
|
+
regfree(®ex);
|
|
1615
|
+
} else {
|
|
1616
|
+
/* Regex compilation failed, return original */
|
|
1617
|
+
result = strdup(value);
|
|
1618
|
+
}
|
|
1619
|
+
free(old_pattern);
|
|
1620
|
+
} else {
|
|
1621
|
+
/* Simple string replacement */
|
|
1622
|
+
size_t old_len = strlen(old_pattern);
|
|
1623
|
+
size_t new_len = strlen(new_str);
|
|
1624
|
+
size_t value_len = strlen(value);
|
|
1625
|
+
|
|
1626
|
+
/* Count occurrences */
|
|
1627
|
+
size_t count = 0;
|
|
1628
|
+
const char *p = value;
|
|
1629
|
+
while ((p = strstr(p, old_pattern)) != NULL) {
|
|
1630
|
+
count++;
|
|
1631
|
+
p += old_len;
|
|
1632
|
+
}
|
|
1633
|
+
|
|
1634
|
+
if (count == 0) {
|
|
1635
|
+
result = strdup(value);
|
|
1636
|
+
} else {
|
|
1637
|
+
size_t result_len = value_len + count * (new_len > old_len ? (new_len - old_len) : 0);
|
|
1638
|
+
result = malloc(result_len + 1);
|
|
1639
|
+
if (result) {
|
|
1640
|
+
char *out = result;
|
|
1641
|
+
const char *src = value;
|
|
1642
|
+
const char *next;
|
|
1643
|
+
|
|
1644
|
+
while ((next = strstr(src, old_pattern)) != NULL) {
|
|
1645
|
+
/* Copy text before match */
|
|
1646
|
+
size_t before_len = next - src;
|
|
1647
|
+
memcpy(out, src, before_len);
|
|
1648
|
+
out += before_len;
|
|
1649
|
+
|
|
1650
|
+
/* Copy replacement */
|
|
1651
|
+
memcpy(out, new_str, new_len);
|
|
1652
|
+
out += new_len;
|
|
1653
|
+
|
|
1654
|
+
src = next + old_len;
|
|
1655
|
+
}
|
|
1656
|
+
|
|
1657
|
+
/* Copy remaining text */
|
|
1658
|
+
strcpy(out, src);
|
|
1659
|
+
} else {
|
|
1660
|
+
/* Malloc failed, return original */
|
|
1661
|
+
result = strdup(value);
|
|
1662
|
+
}
|
|
1663
|
+
}
|
|
1664
|
+
free(old_pattern);
|
|
1665
|
+
}
|
|
1666
|
+
|
|
1667
|
+
free(options_copy);
|
|
1668
|
+
if (!result) return strdup(value);
|
|
1669
|
+
return result;
|
|
1670
|
+
}
|
|
1671
|
+
|
|
1672
|
+
if (strcmp(transform_name, "substring") == 0 || strcmp(transform_name, "substr") == 0) {
|
|
1673
|
+
if (!options) return strdup(value);
|
|
1674
|
+
|
|
1675
|
+
long start = 0, end = -1;
|
|
1676
|
+
if (sscanf(options, "%ld,%ld", &start, &end) >= 1) {
|
|
1677
|
+
size_t len = strlen(value);
|
|
1678
|
+
if (start < 0) start = len + start; /* Negative index from end */
|
|
1679
|
+
if (end < 0) end = len + end;
|
|
1680
|
+
if (start < 0) start = 0;
|
|
1681
|
+
if (end < 0) end = (long)len;
|
|
1682
|
+
if (start > (long)len) start = len;
|
|
1683
|
+
if (end > (long)len) end = len;
|
|
1684
|
+
if (start > end) return strdup("");
|
|
1685
|
+
|
|
1686
|
+
size_t substr_len = end - start;
|
|
1687
|
+
char *result = malloc(substr_len + 1);
|
|
1688
|
+
if (!result) return NULL;
|
|
1689
|
+
memcpy(result, value + start, substr_len);
|
|
1690
|
+
result[substr_len] = '\0';
|
|
1691
|
+
return result;
|
|
1692
|
+
}
|
|
1693
|
+
return strdup(value);
|
|
1694
|
+
}
|
|
1695
|
+
|
|
1696
|
+
if (strcmp(transform_name, "truncate") == 0) {
|
|
1697
|
+
if (!options) return strdup(value);
|
|
1698
|
+
|
|
1699
|
+
long max_len = 0;
|
|
1700
|
+
char suffix[64] = "";
|
|
1701
|
+
if (sscanf(options, "%ld,%63s", &max_len, suffix) >= 1 ||
|
|
1702
|
+
sscanf(options, "%ld", &max_len) == 1) {
|
|
1703
|
+
size_t len = strlen(value);
|
|
1704
|
+
if ((long)len <= max_len) return strdup(value);
|
|
1705
|
+
|
|
1706
|
+
size_t suffix_len = strlen(suffix);
|
|
1707
|
+
size_t trunc_len = max_len > (long)suffix_len ? max_len - suffix_len : max_len;
|
|
1708
|
+
|
|
1709
|
+
char *result = malloc(trunc_len + suffix_len + 1);
|
|
1710
|
+
if (!result) return NULL;
|
|
1711
|
+
memcpy(result, value, trunc_len);
|
|
1712
|
+
memcpy(result + trunc_len, suffix, suffix_len);
|
|
1713
|
+
result[trunc_len + suffix_len] = '\0';
|
|
1714
|
+
return result;
|
|
1715
|
+
}
|
|
1716
|
+
return strdup(value);
|
|
1717
|
+
}
|
|
1718
|
+
|
|
1719
|
+
if (strcmp(transform_name, "default") == 0) {
|
|
1720
|
+
if (value[0] == '\0') {
|
|
1721
|
+
return options ? strdup(options) : strdup("");
|
|
1722
|
+
}
|
|
1723
|
+
return strdup(value);
|
|
1724
|
+
}
|
|
1725
|
+
|
|
1726
|
+
if (strcmp(transform_name, "escape") == 0 || strcmp(transform_name, "html_escape") == 0) {
|
|
1727
|
+
size_t len = strlen(value);
|
|
1728
|
+
size_t result_cap = len * 6 + 1; /* Worst case: every char becomes &xxxx; */
|
|
1729
|
+
char *result = malloc(result_cap);
|
|
1730
|
+
if (!result) return NULL;
|
|
1731
|
+
|
|
1732
|
+
char *out = result;
|
|
1733
|
+
for (const char *p = value; *p; p++) {
|
|
1734
|
+
unsigned char c = (unsigned char)*p;
|
|
1735
|
+
switch (c) {
|
|
1736
|
+
case '&': strcpy(out, "&"); out += 5; break;
|
|
1737
|
+
case '<': strcpy(out, "<"); out += 4; break;
|
|
1738
|
+
case '>': strcpy(out, ">"); out += 4; break;
|
|
1739
|
+
case '"': strcpy(out, """); out += 6; break;
|
|
1740
|
+
case '\'': strcpy(out, "'"); out += 5; break;
|
|
1741
|
+
default:
|
|
1742
|
+
if (c >= 32 && c < 127) {
|
|
1743
|
+
*out++ = (char)c;
|
|
1744
|
+
} else {
|
|
1745
|
+
/* Encode other characters as entities */
|
|
1746
|
+
int written = snprintf(out, 16, "&#%u;", c);
|
|
1747
|
+
if (written > 0) out += written;
|
|
1748
|
+
}
|
|
1749
|
+
break;
|
|
1750
|
+
}
|
|
1751
|
+
}
|
|
1752
|
+
*out = '\0';
|
|
1753
|
+
return result;
|
|
1754
|
+
}
|
|
1755
|
+
|
|
1756
|
+
if (strcmp(transform_name, "basename") == 0) {
|
|
1757
|
+
const char *last_slash = strrchr(value, '/');
|
|
1758
|
+
if (last_slash) {
|
|
1759
|
+
return strdup(last_slash + 1);
|
|
1760
|
+
}
|
|
1761
|
+
return strdup(value);
|
|
1762
|
+
}
|
|
1763
|
+
|
|
1764
|
+
if (strcmp(transform_name, "urlencode") == 0) {
|
|
1765
|
+
size_t len = strlen(value);
|
|
1766
|
+
char *result = malloc(len * 3 + 1); /* Worst case: every char becomes %XX */
|
|
1767
|
+
if (!result) return NULL;
|
|
1768
|
+
|
|
1769
|
+
char *out = result;
|
|
1770
|
+
for (const char *p = value; *p; p++) {
|
|
1771
|
+
unsigned char c = (unsigned char)*p;
|
|
1772
|
+
if (isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') {
|
|
1773
|
+
*out++ = (char)c;
|
|
1774
|
+
} else {
|
|
1775
|
+
int written = snprintf(out, 8, "%%%02X", c);
|
|
1776
|
+
if (written > 0) out += written;
|
|
1777
|
+
}
|
|
1778
|
+
}
|
|
1779
|
+
*out = '\0';
|
|
1780
|
+
return result;
|
|
1781
|
+
}
|
|
1782
|
+
|
|
1783
|
+
if (strcmp(transform_name, "urldecode") == 0) {
|
|
1784
|
+
size_t len = strlen(value);
|
|
1785
|
+
char *result = malloc(len + 1);
|
|
1786
|
+
if (!result) return NULL;
|
|
1787
|
+
|
|
1788
|
+
char *out = result;
|
|
1789
|
+
for (const char *p = value; *p; p++) {
|
|
1790
|
+
if (*p == '%' && isxdigit((unsigned char)p[1]) && isxdigit((unsigned char)p[2])) {
|
|
1791
|
+
char hex[3] = {p[1], p[2], '\0'};
|
|
1792
|
+
*out++ = (char)strtoul(hex, NULL, 16);
|
|
1793
|
+
p += 2;
|
|
1794
|
+
} else if (*p == '+') {
|
|
1795
|
+
*out++ = ' ';
|
|
1796
|
+
} else {
|
|
1797
|
+
*out++ = *p;
|
|
1798
|
+
}
|
|
1799
|
+
}
|
|
1800
|
+
*out = '\0';
|
|
1801
|
+
return result;
|
|
1802
|
+
}
|
|
1803
|
+
|
|
1804
|
+
if (strcmp(transform_name, "prefix") == 0) {
|
|
1805
|
+
if (!options) return strdup(value);
|
|
1806
|
+
size_t prefix_len = strlen(options);
|
|
1807
|
+
size_t value_len = strlen(value);
|
|
1808
|
+
char *result = malloc(prefix_len + value_len + 1);
|
|
1809
|
+
if (!result) return NULL;
|
|
1810
|
+
memcpy(result, options, prefix_len);
|
|
1811
|
+
memcpy(result + prefix_len, value, value_len);
|
|
1812
|
+
result[prefix_len + value_len] = '\0';
|
|
1813
|
+
return result;
|
|
1814
|
+
}
|
|
1815
|
+
|
|
1816
|
+
if (strcmp(transform_name, "suffix") == 0) {
|
|
1817
|
+
if (!options) return strdup(value);
|
|
1818
|
+
size_t suffix_len = strlen(options);
|
|
1819
|
+
size_t value_len = strlen(value);
|
|
1820
|
+
char *result = malloc(value_len + suffix_len + 1);
|
|
1821
|
+
if (!result) return NULL;
|
|
1822
|
+
memcpy(result, value, value_len);
|
|
1823
|
+
memcpy(result + value_len, options, suffix_len);
|
|
1824
|
+
result[value_len + suffix_len] = '\0';
|
|
1825
|
+
return result;
|
|
1826
|
+
}
|
|
1827
|
+
|
|
1828
|
+
if (strcmp(transform_name, "remove") == 0) {
|
|
1829
|
+
if (!options) return strdup(value);
|
|
1830
|
+
|
|
1831
|
+
size_t remove_len = strlen(options);
|
|
1832
|
+
if (remove_len == 0) return strdup(value);
|
|
1833
|
+
|
|
1834
|
+
size_t value_len = strlen(value);
|
|
1835
|
+
char *result = malloc(value_len + 1);
|
|
1836
|
+
if (!result) return NULL;
|
|
1837
|
+
|
|
1838
|
+
char *out = result;
|
|
1839
|
+
for (const char *p = value; *p; p++) {
|
|
1840
|
+
if (strncmp(p, options, remove_len) == 0) {
|
|
1841
|
+
p += remove_len - 1; /* -1 because loop increments */
|
|
1842
|
+
} else {
|
|
1843
|
+
*out++ = *p;
|
|
1844
|
+
}
|
|
1845
|
+
}
|
|
1846
|
+
*out = '\0';
|
|
1847
|
+
return result;
|
|
1848
|
+
}
|
|
1849
|
+
|
|
1850
|
+
if (strcmp(transform_name, "repeat") == 0) {
|
|
1851
|
+
if (!options) return strdup(value);
|
|
1852
|
+
|
|
1853
|
+
long count = 1;
|
|
1854
|
+
if (sscanf(options, "%ld", &count) == 1 && count > 0) {
|
|
1855
|
+
size_t value_len = strlen(value);
|
|
1856
|
+
if (value_len == 0) return strdup("");
|
|
1857
|
+
|
|
1858
|
+
char *result = malloc(value_len * count + 1);
|
|
1859
|
+
if (!result) return NULL;
|
|
1860
|
+
|
|
1861
|
+
char *out = result;
|
|
1862
|
+
for (long i = 0; i < count; i++) {
|
|
1863
|
+
memcpy(out, value, value_len);
|
|
1864
|
+
out += value_len;
|
|
1865
|
+
}
|
|
1866
|
+
*out = '\0';
|
|
1867
|
+
return result;
|
|
1868
|
+
}
|
|
1869
|
+
return strdup(value);
|
|
1870
|
+
}
|
|
1871
|
+
|
|
1872
|
+
if (strcmp(transform_name, "reverse") == 0) {
|
|
1873
|
+
size_t len = strlen(value);
|
|
1874
|
+
char *result = malloc(len + 1);
|
|
1875
|
+
if (!result) return NULL;
|
|
1876
|
+
|
|
1877
|
+
for (size_t i = 0; i < len; i++) {
|
|
1878
|
+
result[i] = value[len - 1 - i];
|
|
1879
|
+
}
|
|
1880
|
+
result[len] = '\0';
|
|
1881
|
+
return result;
|
|
1882
|
+
}
|
|
1883
|
+
|
|
1884
|
+
if (strcmp(transform_name, "format") == 0) {
|
|
1885
|
+
if (!options) return strdup(value);
|
|
1886
|
+
|
|
1887
|
+
/* Try to parse as number for formatting */
|
|
1888
|
+
double num = 0.0;
|
|
1889
|
+
if (sscanf(value, "%lf", &num) == 1) {
|
|
1890
|
+
char buffer[256];
|
|
1891
|
+
int written = snprintf(buffer, sizeof(buffer), options, num);
|
|
1892
|
+
if (written > 0 && written < (int)sizeof(buffer)) {
|
|
1893
|
+
return strdup(buffer);
|
|
1894
|
+
}
|
|
1895
|
+
}
|
|
1896
|
+
/* If not a number or format failed, return original */
|
|
1897
|
+
return strdup(value);
|
|
1898
|
+
}
|
|
1899
|
+
|
|
1900
|
+
if (strcmp(transform_name, "length") == 0) {
|
|
1901
|
+
char buffer[32];
|
|
1902
|
+
snprintf(buffer, sizeof(buffer), "%zu", strlen(value));
|
|
1903
|
+
return strdup(buffer);
|
|
1904
|
+
}
|
|
1905
|
+
|
|
1906
|
+
if (strcmp(transform_name, "pad") == 0) {
|
|
1907
|
+
if (!options) return strdup(value);
|
|
1908
|
+
|
|
1909
|
+
long width = 0;
|
|
1910
|
+
char pad_char = ' ';
|
|
1911
|
+
if (sscanf(options, "%ld,%c", &width, &pad_char) >= 1 ||
|
|
1912
|
+
sscanf(options, "%ld", &width) == 1) {
|
|
1913
|
+
size_t len = strlen(value);
|
|
1914
|
+
if ((long)len >= width) return strdup(value);
|
|
1915
|
+
|
|
1916
|
+
size_t pad_count = width - len;
|
|
1917
|
+
char *result = malloc(width + 1);
|
|
1918
|
+
if (!result) return NULL;
|
|
1919
|
+
|
|
1920
|
+
memset(result, pad_char, pad_count);
|
|
1921
|
+
memcpy(result + pad_count, value, len);
|
|
1922
|
+
result[width] = '\0';
|
|
1923
|
+
return result;
|
|
1924
|
+
}
|
|
1925
|
+
return strdup(value);
|
|
1926
|
+
}
|
|
1927
|
+
|
|
1928
|
+
if (strcmp(transform_name, "contains") == 0) {
|
|
1929
|
+
if (!options) return strdup("false");
|
|
1930
|
+
return strstr(value, options) ? strdup("true") : strdup("false");
|
|
1931
|
+
}
|
|
1932
|
+
|
|
1933
|
+
/* Unknown transform, return original */
|
|
1934
|
+
return strdup(value);
|
|
1935
|
+
}
|
|
1936
|
+
|
|
1937
|
+
/**
|
|
1938
|
+
* Apply transform chain to a value
|
|
1939
|
+
* Returns newly allocated string
|
|
1940
|
+
*/
|
|
1941
|
+
static char *apply_transform_chain(const char *value, apex_transform *chain) {
|
|
1942
|
+
if (!value || !chain) return strdup(value ? value : "");
|
|
1943
|
+
|
|
1944
|
+
char *current_value = strdup(value);
|
|
1945
|
+
apex_string_array *array = NULL;
|
|
1946
|
+
bool is_array = false;
|
|
1947
|
+
|
|
1948
|
+
apex_transform *transform = chain;
|
|
1949
|
+
while (transform) {
|
|
1950
|
+
char *new_value = apply_transform(transform->name, transform->options,
|
|
1951
|
+
current_value, &array, &is_array);
|
|
1952
|
+
if (!new_value) {
|
|
1953
|
+
/* Transform failed, return original value */
|
|
1954
|
+
free(current_value);
|
|
1955
|
+
if (array) free_string_array(array);
|
|
1956
|
+
return strdup(value ? value : "");
|
|
1957
|
+
}
|
|
1958
|
+
|
|
1959
|
+
if (current_value != value) { /* Don't free original value */
|
|
1960
|
+
free(current_value);
|
|
1961
|
+
}
|
|
1962
|
+
current_value = new_value;
|
|
1963
|
+
|
|
1964
|
+
transform = transform->next;
|
|
1965
|
+
}
|
|
1966
|
+
|
|
1967
|
+
if (array) free_string_array(array);
|
|
1968
|
+
|
|
1969
|
+
return current_value;
|
|
1970
|
+
}
|
|
1971
|
+
|
|
1972
|
+
/**
|
|
1973
|
+
* Replace [%key] patterns with metadata values
|
|
1974
|
+
* If options->enable_metadata_transforms is true, supports [%key:transform:transform2] syntax
|
|
1975
|
+
*/
|
|
1976
|
+
char *apex_metadata_replace_variables(const char *text, apex_metadata_item *metadata, const apex_options *options) {
|
|
1977
|
+
if (!text || !metadata) {
|
|
1978
|
+
return text ? strdup(text) : NULL;
|
|
1979
|
+
}
|
|
1980
|
+
|
|
1981
|
+
bool transforms_enabled = options && options->enable_metadata_transforms;
|
|
1982
|
+
|
|
1983
|
+
/* Build result incrementally */
|
|
1984
|
+
size_t result_capacity = strlen(text) + 512; /* Extra space for potential expansions */
|
|
1985
|
+
char *result = malloc(result_capacity);
|
|
1986
|
+
if (!result) return NULL;
|
|
1987
|
+
|
|
1988
|
+
size_t result_len = 0;
|
|
1989
|
+
const char *last_pos = text; /* Track position in original text */
|
|
1990
|
+
|
|
1991
|
+
const char *p = text;
|
|
1992
|
+
while ((p = strstr(p, "[%")) != NULL) {
|
|
1993
|
+
/* Find the matching closing ']' for '[%', accounting for brackets inside the pattern */
|
|
1994
|
+
/* Since '[%' opens a bracket, we need to find the matching ']' */
|
|
1995
|
+
/* We scan forward and track bracket depth to handle nested brackets in regex patterns */
|
|
1996
|
+
const char *end = p + 2;
|
|
1997
|
+
int bracket_depth = 1; /* Start at 1 because '[%' opened a bracket */
|
|
1998
|
+
|
|
1999
|
+
while (*end && bracket_depth > 0) {
|
|
2000
|
+
if (*end == '[') {
|
|
2001
|
+
bracket_depth++;
|
|
2002
|
+
} else if (*end == ']') {
|
|
2003
|
+
bracket_depth--;
|
|
2004
|
+
}
|
|
2005
|
+
if (bracket_depth > 0) {
|
|
2006
|
+
end++;
|
|
2007
|
+
}
|
|
2008
|
+
}
|
|
2009
|
+
|
|
2010
|
+
if (!*end && bracket_depth > 0) {
|
|
2011
|
+
/* Malformed - no matching closing bracket found */
|
|
2012
|
+
break;
|
|
2013
|
+
}
|
|
2014
|
+
|
|
2015
|
+
/* Copy text before [%...] */
|
|
2016
|
+
size_t prefix_len = p - last_pos;
|
|
2017
|
+
if (prefix_len > 0) {
|
|
2018
|
+
if (result_len + prefix_len + 1 > result_capacity) {
|
|
2019
|
+
result_capacity = (result_len + prefix_len + 1) * 2;
|
|
2020
|
+
char *new_result = realloc(result, result_capacity);
|
|
2021
|
+
if (!new_result) {
|
|
2022
|
+
free(result);
|
|
2023
|
+
return NULL;
|
|
2024
|
+
}
|
|
2025
|
+
result = new_result;
|
|
2026
|
+
}
|
|
2027
|
+
memcpy(result + result_len, last_pos, prefix_len);
|
|
2028
|
+
result_len += prefix_len;
|
|
2029
|
+
}
|
|
2030
|
+
|
|
2031
|
+
/* Extract key and transforms */
|
|
2032
|
+
size_t pattern_len = end - (p + 2);
|
|
2033
|
+
char pattern[512];
|
|
2034
|
+
if (pattern_len >= sizeof(pattern)) {
|
|
2035
|
+
/* Pattern too long, keep original */
|
|
2036
|
+
size_t keep_len = (end - p) + 1;
|
|
2037
|
+
if (result_len + keep_len + 1 > result_capacity) {
|
|
2038
|
+
result_capacity = (result_len + keep_len + 1) * 2;
|
|
2039
|
+
char *new_result = realloc(result, result_capacity);
|
|
2040
|
+
if (!new_result) {
|
|
2041
|
+
free(result);
|
|
2042
|
+
return NULL;
|
|
2043
|
+
}
|
|
2044
|
+
result = new_result;
|
|
2045
|
+
}
|
|
2046
|
+
memcpy(result + result_len, p, keep_len);
|
|
2047
|
+
result_len += keep_len;
|
|
2048
|
+
last_pos = end + 1;
|
|
2049
|
+
p = end + 1;
|
|
2050
|
+
continue;
|
|
2051
|
+
}
|
|
2052
|
+
|
|
2053
|
+
memcpy(pattern, p + 2, pattern_len);
|
|
2054
|
+
pattern[pattern_len] = '\0';
|
|
2055
|
+
|
|
2056
|
+
/* Parse key and transforms */
|
|
2057
|
+
char *key = NULL;
|
|
2058
|
+
apex_transform *transform_chain = NULL;
|
|
2059
|
+
const char *value = NULL;
|
|
2060
|
+
char *transformed_value = NULL;
|
|
2061
|
+
|
|
2062
|
+
if (transforms_enabled && strchr(pattern, ':')) {
|
|
2063
|
+
/* Has transforms */
|
|
2064
|
+
transform_chain = parse_transform_chain(pattern, &key);
|
|
2065
|
+
if (key) {
|
|
2066
|
+
value = apex_metadata_get(metadata, key);
|
|
2067
|
+
if (value && transform_chain) {
|
|
2068
|
+
transformed_value = apply_transform_chain(value, transform_chain);
|
|
2069
|
+
/* If transform chain failed, fall back to original value */
|
|
2070
|
+
if (!transformed_value) {
|
|
2071
|
+
transformed_value = strdup(value);
|
|
2072
|
+
}
|
|
2073
|
+
} else if (value) {
|
|
2074
|
+
transformed_value = strdup(value);
|
|
2075
|
+
}
|
|
2076
|
+
free(key);
|
|
2077
|
+
free_transform_chain(transform_chain);
|
|
2078
|
+
} else {
|
|
2079
|
+
/* Parse failed, treat as simple key */
|
|
2080
|
+
value = apex_metadata_get(metadata, pattern);
|
|
2081
|
+
if (value) {
|
|
2082
|
+
transformed_value = strdup(value);
|
|
2083
|
+
}
|
|
2084
|
+
}
|
|
2085
|
+
} else {
|
|
2086
|
+
/* Simple key, no transforms */
|
|
2087
|
+
value = apex_metadata_get(metadata, pattern);
|
|
2088
|
+
if (value) {
|
|
2089
|
+
transformed_value = strdup(value);
|
|
2090
|
+
}
|
|
2091
|
+
}
|
|
2092
|
+
|
|
2093
|
+
if (transformed_value) {
|
|
2094
|
+
size_t value_len = strlen(transformed_value);
|
|
2095
|
+
if (result_len + value_len + 1 > result_capacity) {
|
|
2096
|
+
result_capacity = (result_len + value_len + 1) * 2;
|
|
2097
|
+
char *new_result = realloc(result, result_capacity);
|
|
2098
|
+
if (!new_result) {
|
|
2099
|
+
free(result);
|
|
2100
|
+
free(transformed_value);
|
|
2101
|
+
return NULL;
|
|
2102
|
+
}
|
|
2103
|
+
result = new_result;
|
|
2104
|
+
}
|
|
2105
|
+
memcpy(result + result_len, transformed_value, value_len);
|
|
2106
|
+
result_len += value_len;
|
|
2107
|
+
free(transformed_value);
|
|
2108
|
+
} else {
|
|
2109
|
+
/* Keep original pattern if not found */
|
|
2110
|
+
size_t keep_len = (end - p) + 1;
|
|
2111
|
+
if (result_len + keep_len + 1 > result_capacity) {
|
|
2112
|
+
result_capacity = (result_len + keep_len + 1) * 2;
|
|
2113
|
+
char *new_result = realloc(result, result_capacity);
|
|
2114
|
+
if (!new_result) {
|
|
2115
|
+
free(result);
|
|
2116
|
+
return NULL;
|
|
2117
|
+
}
|
|
2118
|
+
result = new_result;
|
|
2119
|
+
}
|
|
2120
|
+
memcpy(result + result_len, p, keep_len);
|
|
2121
|
+
result_len += keep_len;
|
|
2122
|
+
}
|
|
2123
|
+
|
|
2124
|
+
last_pos = end + 1;
|
|
2125
|
+
p = end + 1;
|
|
2126
|
+
}
|
|
2127
|
+
|
|
2128
|
+
/* Copy remaining text after last [%...] */
|
|
2129
|
+
if (last_pos && *last_pos) {
|
|
2130
|
+
size_t remaining = strlen(last_pos);
|
|
2131
|
+
if (result_len + remaining + 1 > result_capacity) {
|
|
2132
|
+
result_capacity = result_len + remaining + 1;
|
|
2133
|
+
char *new_result = realloc(result, result_capacity);
|
|
2134
|
+
if (!new_result) {
|
|
2135
|
+
free(result);
|
|
2136
|
+
return NULL;
|
|
2137
|
+
}
|
|
2138
|
+
result = new_result;
|
|
2139
|
+
}
|
|
2140
|
+
strcpy(result + result_len, last_pos);
|
|
2141
|
+
result_len += remaining;
|
|
2142
|
+
}
|
|
2143
|
+
|
|
2144
|
+
result[result_len] = '\0';
|
|
2145
|
+
return result;
|
|
2146
|
+
}
|
|
2147
|
+
|
|
2148
|
+
/**
|
|
2149
|
+
* Load metadata from a file
|
|
2150
|
+
* Auto-detects format based on first characters
|
|
2151
|
+
*/
|
|
2152
|
+
apex_metadata_item *apex_load_metadata_from_file(const char *filepath) {
|
|
2153
|
+
if (!filepath) return NULL;
|
|
2154
|
+
|
|
2155
|
+
FILE *fp = fopen(filepath, "r");
|
|
2156
|
+
if (!fp) return NULL;
|
|
2157
|
+
|
|
2158
|
+
/* Read file into buffer */
|
|
2159
|
+
fseek(fp, 0, SEEK_END);
|
|
2160
|
+
long file_size = ftell(fp);
|
|
2161
|
+
fseek(fp, 0, SEEK_SET);
|
|
2162
|
+
|
|
2163
|
+
if (file_size < 0 || file_size > 1024 * 1024) { /* Max 1MB */
|
|
2164
|
+
fclose(fp);
|
|
2165
|
+
return NULL;
|
|
2166
|
+
}
|
|
2167
|
+
|
|
2168
|
+
char *buffer = malloc(file_size + 1);
|
|
2169
|
+
if (!buffer) {
|
|
2170
|
+
fclose(fp);
|
|
2171
|
+
return NULL;
|
|
2172
|
+
}
|
|
2173
|
+
|
|
2174
|
+
size_t bytes_read = fread(buffer, 1, file_size, fp);
|
|
2175
|
+
buffer[bytes_read] = '\0';
|
|
2176
|
+
fclose(fp);
|
|
2177
|
+
|
|
2178
|
+
apex_metadata_item *items = NULL;
|
|
2179
|
+
size_t consumed = 0;
|
|
2180
|
+
|
|
2181
|
+
/* Auto-detect format */
|
|
2182
|
+
if (strncmp(buffer, "---", 3) == 0) {
|
|
2183
|
+
/* YAML format */
|
|
2184
|
+
items = parse_yaml_metadata(buffer, &consumed);
|
|
2185
|
+
} else if (buffer[0] == '%') {
|
|
2186
|
+
/* Pandoc format */
|
|
2187
|
+
items = parse_pandoc_metadata(buffer, &consumed);
|
|
2188
|
+
} else {
|
|
2189
|
+
/* MMD format (default) */
|
|
2190
|
+
items = parse_mmd_metadata(buffer, &consumed);
|
|
2191
|
+
}
|
|
2192
|
+
|
|
2193
|
+
free(buffer);
|
|
2194
|
+
return items;
|
|
2195
|
+
}
|
|
2196
|
+
|
|
2197
|
+
/**
|
|
2198
|
+
* Parse a single KEY=VALUE pair, handling quoted values
|
|
2199
|
+
* Returns key and value in allocated strings (caller must free)
|
|
2200
|
+
*/
|
|
2201
|
+
static int parse_key_value_pair(const char *input, char **key_out, char **value_out) {
|
|
2202
|
+
if (!input) return 0;
|
|
2203
|
+
|
|
2204
|
+
const char *equals = strchr(input, '=');
|
|
2205
|
+
if (!equals) return 0;
|
|
2206
|
+
|
|
2207
|
+
/* Extract key */
|
|
2208
|
+
size_t key_len = equals - input;
|
|
2209
|
+
*key_out = malloc(key_len + 1);
|
|
2210
|
+
if (!*key_out) return 0;
|
|
2211
|
+
memcpy(*key_out, input, key_len);
|
|
2212
|
+
(*key_out)[key_len] = '\0';
|
|
2213
|
+
trim_whitespace(*key_out);
|
|
2214
|
+
|
|
2215
|
+
/* Extract value */
|
|
2216
|
+
const char *value_start = equals + 1;
|
|
2217
|
+
|
|
2218
|
+
/* Check if value is quoted */
|
|
2219
|
+
if (*value_start == '"' || *value_start == '\'') {
|
|
2220
|
+
char quote = *value_start;
|
|
2221
|
+
value_start++;
|
|
2222
|
+
const char *value_end = strchr(value_start, quote);
|
|
2223
|
+
if (!value_end) {
|
|
2224
|
+
/* Unmatched quote, treat rest as value */
|
|
2225
|
+
*value_out = strdup(value_start);
|
|
2226
|
+
return 1;
|
|
2227
|
+
}
|
|
2228
|
+
size_t value_len = value_end - value_start;
|
|
2229
|
+
*value_out = malloc(value_len + 1);
|
|
2230
|
+
if (!*value_out) {
|
|
2231
|
+
free(*key_out);
|
|
2232
|
+
return 0;
|
|
2233
|
+
}
|
|
2234
|
+
memcpy(*value_out, value_start, value_len);
|
|
2235
|
+
(*value_out)[value_len] = '\0';
|
|
2236
|
+
} else {
|
|
2237
|
+
/* Unquoted value - take until comma or end */
|
|
2238
|
+
const char *value_end = strchr(value_start, ',');
|
|
2239
|
+
if (!value_end) {
|
|
2240
|
+
value_end = value_start + strlen(value_start);
|
|
2241
|
+
}
|
|
2242
|
+
size_t value_len = value_end - value_start;
|
|
2243
|
+
*value_out = malloc(value_len + 1);
|
|
2244
|
+
if (!*value_out) {
|
|
2245
|
+
free(*key_out);
|
|
2246
|
+
return 0;
|
|
2247
|
+
}
|
|
2248
|
+
memcpy(*value_out, value_start, value_len);
|
|
2249
|
+
(*value_out)[value_len] = '\0';
|
|
2250
|
+
trim_whitespace(*value_out);
|
|
2251
|
+
}
|
|
2252
|
+
|
|
2253
|
+
return 1;
|
|
2254
|
+
}
|
|
2255
|
+
|
|
2256
|
+
/**
|
|
2257
|
+
* Parse command-line metadata from KEY=VALUE string
|
|
2258
|
+
* Handles quoted values and comma-separated pairs
|
|
2259
|
+
*/
|
|
2260
|
+
apex_metadata_item *apex_parse_command_metadata(const char *arg) {
|
|
2261
|
+
if (!arg || !*arg) return NULL;
|
|
2262
|
+
|
|
2263
|
+
apex_metadata_item *items = NULL;
|
|
2264
|
+
const char *p = arg;
|
|
2265
|
+
|
|
2266
|
+
while (*p) {
|
|
2267
|
+
/* Skip whitespace */
|
|
2268
|
+
while (*p && isspace((unsigned char)*p)) p++;
|
|
2269
|
+
if (!*p) break;
|
|
2270
|
+
|
|
2271
|
+
/* Find the equals sign for this pair */
|
|
2272
|
+
const char *equals = strchr(p, '=');
|
|
2273
|
+
if (!equals) break; /* No equals sign, invalid */
|
|
2274
|
+
|
|
2275
|
+
/* Find where this KEY=VALUE pair ends */
|
|
2276
|
+
const char *value_start = equals + 1;
|
|
2277
|
+
const char *pair_end = NULL;
|
|
2278
|
+
|
|
2279
|
+
/* Check if value is quoted */
|
|
2280
|
+
if (*value_start == '"' || *value_start == '\'') {
|
|
2281
|
+
char quote = *value_start;
|
|
2282
|
+
const char *quote_end = strchr(value_start + 1, quote);
|
|
2283
|
+
if (quote_end) {
|
|
2284
|
+
pair_end = quote_end + 1;
|
|
2285
|
+
} else {
|
|
2286
|
+
/* Unmatched quote - take rest of string */
|
|
2287
|
+
pair_end = p + strlen(p);
|
|
2288
|
+
}
|
|
2289
|
+
} else {
|
|
2290
|
+
/* Unquoted value - find next comma that's followed by KEY= pattern
|
|
2291
|
+
* We look for comma followed by whitespace and then KEY= */
|
|
2292
|
+
const char *search = value_start;
|
|
2293
|
+
pair_end = p + strlen(p); /* Default to end of string */
|
|
2294
|
+
|
|
2295
|
+
const char *comma = strchr(search, ',');
|
|
2296
|
+
if (comma) {
|
|
2297
|
+
/* Found a comma - check if it starts a new KEY= pair */
|
|
2298
|
+
const char *after_comma = comma + 1;
|
|
2299
|
+
while (*after_comma && isspace((unsigned char)*after_comma)) after_comma++;
|
|
2300
|
+
|
|
2301
|
+
/* Look for KEY= pattern (alphanumeric key followed by =) */
|
|
2302
|
+
if ((isalnum((unsigned char)*after_comma) || *after_comma == '_')) {
|
|
2303
|
+
const char *next_equals = strchr(after_comma, '=');
|
|
2304
|
+
if (next_equals) {
|
|
2305
|
+
/* Check if there's another comma between after_comma and next_equals */
|
|
2306
|
+
const char *comma_between = strchr(after_comma, ',');
|
|
2307
|
+
if (!comma_between || comma_between > next_equals) {
|
|
2308
|
+
/* This comma starts the next KEY= pair */
|
|
2309
|
+
pair_end = comma;
|
|
2310
|
+
}
|
|
2311
|
+
}
|
|
2312
|
+
}
|
|
2313
|
+
}
|
|
2314
|
+
/* If no comma or comma doesn't start KEY= pair, pair_end stays at end of string */
|
|
2315
|
+
}
|
|
2316
|
+
|
|
2317
|
+
/* Extract this pair */
|
|
2318
|
+
size_t pair_len = pair_end - p;
|
|
2319
|
+
char *pair = malloc(pair_len + 1);
|
|
2320
|
+
if (!pair) break;
|
|
2321
|
+
memcpy(pair, p, pair_len);
|
|
2322
|
+
pair[pair_len] = '\0';
|
|
2323
|
+
|
|
2324
|
+
/* Parse key=value */
|
|
2325
|
+
char *key = NULL;
|
|
2326
|
+
char *value = NULL;
|
|
2327
|
+
if (parse_key_value_pair(pair, &key, &value)) {
|
|
2328
|
+
if (key && value) {
|
|
2329
|
+
add_metadata_item(&items, key, value);
|
|
2330
|
+
free(key);
|
|
2331
|
+
free(value);
|
|
2332
|
+
}
|
|
2333
|
+
}
|
|
2334
|
+
|
|
2335
|
+
free(pair);
|
|
2336
|
+
|
|
2337
|
+
/* Move to next pair */
|
|
2338
|
+
if (pair_end < p + strlen(p) && *pair_end == ',') {
|
|
2339
|
+
p = pair_end + 1;
|
|
2340
|
+
} else {
|
|
2341
|
+
break;
|
|
2342
|
+
}
|
|
2343
|
+
}
|
|
2344
|
+
|
|
2345
|
+
return items;
|
|
2346
|
+
}
|
|
2347
|
+
|
|
2348
|
+
/**
|
|
2349
|
+
* Merge multiple metadata lists with precedence
|
|
2350
|
+
* Later lists take precedence over earlier ones
|
|
2351
|
+
*/
|
|
2352
|
+
apex_metadata_item *apex_merge_metadata(apex_metadata_item *first, ...) {
|
|
2353
|
+
apex_metadata_item *result = NULL;
|
|
2354
|
+
|
|
2355
|
+
/* Start with a copy of the first list (if any) */
|
|
2356
|
+
apex_metadata_item *src;
|
|
2357
|
+
if (first) {
|
|
2358
|
+
src = first;
|
|
2359
|
+
while (src) {
|
|
2360
|
+
add_metadata_item(&result, src->key, src->value);
|
|
2361
|
+
src = src->next;
|
|
2362
|
+
}
|
|
2363
|
+
}
|
|
2364
|
+
|
|
2365
|
+
/* Merge remaining lists */
|
|
2366
|
+
va_list args;
|
|
2367
|
+
va_start(args, first);
|
|
2368
|
+
|
|
2369
|
+
apex_metadata_item *next_list;
|
|
2370
|
+
while ((next_list = va_arg(args, apex_metadata_item*)) != NULL) {
|
|
2371
|
+
/* For each item in next_list, add or replace in result */
|
|
2372
|
+
src = next_list;
|
|
2373
|
+
while (src) {
|
|
2374
|
+
/* Remove existing item with same key (case-insensitive) */
|
|
2375
|
+
apex_metadata_item *prev = NULL;
|
|
2376
|
+
apex_metadata_item *curr = result;
|
|
2377
|
+
while (curr) {
|
|
2378
|
+
if (strcasecmp(curr->key, src->key) == 0) {
|
|
2379
|
+
/* Remove this item */
|
|
2380
|
+
if (prev) {
|
|
2381
|
+
prev->next = curr->next;
|
|
2382
|
+
} else {
|
|
2383
|
+
result = curr->next;
|
|
2384
|
+
}
|
|
2385
|
+
free(curr->key);
|
|
2386
|
+
free(curr->value);
|
|
2387
|
+
apex_metadata_item *to_free = curr;
|
|
2388
|
+
curr = curr->next;
|
|
2389
|
+
free(to_free);
|
|
2390
|
+
break;
|
|
2391
|
+
}
|
|
2392
|
+
prev = curr;
|
|
2393
|
+
curr = curr->next;
|
|
2394
|
+
}
|
|
2395
|
+
/* Add new item */
|
|
2396
|
+
add_metadata_item(&result, src->key, src->value);
|
|
2397
|
+
src = src->next;
|
|
2398
|
+
}
|
|
2399
|
+
}
|
|
2400
|
+
|
|
2401
|
+
va_end(args);
|
|
2402
|
+
return result;
|
|
2403
|
+
}
|
|
2404
|
+
|
|
2405
|
+
/**
|
|
2406
|
+
* Helper function to check if a string represents a true value
|
|
2407
|
+
*/
|
|
2408
|
+
static bool is_true_value(const char *value) {
|
|
2409
|
+
if (!value) return false;
|
|
2410
|
+
/* Downcase the value for comparison */
|
|
2411
|
+
char *lower = strdup(value);
|
|
2412
|
+
if (!lower) return false;
|
|
2413
|
+
for (char *p = lower; *p; p++) {
|
|
2414
|
+
*p = (char)tolower((unsigned char)*p);
|
|
2415
|
+
}
|
|
2416
|
+
bool result = (strcmp(lower, "true") == 0 ||
|
|
2417
|
+
strcmp(lower, "yes") == 0 ||
|
|
2418
|
+
strcmp(lower, "1") == 0);
|
|
2419
|
+
free(lower);
|
|
2420
|
+
return result;
|
|
2421
|
+
}
|
|
2422
|
+
|
|
2423
|
+
/**
|
|
2424
|
+
* Helper function to check if a string represents a false value
|
|
2425
|
+
*/
|
|
2426
|
+
static bool is_false_value(const char *value) {
|
|
2427
|
+
if (!value) return false;
|
|
2428
|
+
/* Downcase the value for comparison */
|
|
2429
|
+
char *lower = strdup(value);
|
|
2430
|
+
if (!lower) return false;
|
|
2431
|
+
for (char *p = lower; *p; p++) {
|
|
2432
|
+
*p = (char)tolower((unsigned char)*p);
|
|
2433
|
+
}
|
|
2434
|
+
bool result = (strcmp(lower, "false") == 0 ||
|
|
2435
|
+
strcmp(lower, "no") == 0 ||
|
|
2436
|
+
strcmp(lower, "0") == 0);
|
|
2437
|
+
free(lower);
|
|
2438
|
+
return result;
|
|
2439
|
+
}
|
|
2440
|
+
|
|
2441
|
+
/**
|
|
2442
|
+
* Apply metadata values to apex_options structure
|
|
2443
|
+
* Maps metadata keys to command-line options, allowing per-document control
|
|
2444
|
+
*/
|
|
2445
|
+
void apex_apply_metadata_to_options(apex_metadata_item *metadata, apex_options *options) {
|
|
2446
|
+
if (!metadata || !options) return;
|
|
2447
|
+
|
|
2448
|
+
/* First pass: handle mode if specified (this resets options to mode defaults) */
|
|
2449
|
+
apex_metadata_item *item = metadata;
|
|
2450
|
+
while (item) {
|
|
2451
|
+
const char *key = item->key;
|
|
2452
|
+
const char *value = item->value;
|
|
2453
|
+
|
|
2454
|
+
if (key && value && strcasecmp(key, "mode") == 0) {
|
|
2455
|
+
/* Convert string to enum and apply mode (this resets all options) */
|
|
2456
|
+
char *lower = strdup(value);
|
|
2457
|
+
if (lower) {
|
|
2458
|
+
for (char *p = lower; *p; p++) {
|
|
2459
|
+
*p = (char)tolower((unsigned char)*p);
|
|
2460
|
+
}
|
|
2461
|
+
if (strcmp(lower, "commonmark") == 0) {
|
|
2462
|
+
*options = apex_options_for_mode(APEX_MODE_COMMONMARK);
|
|
2463
|
+
} else if (strcmp(lower, "gfm") == 0) {
|
|
2464
|
+
*options = apex_options_for_mode(APEX_MODE_GFM);
|
|
2465
|
+
} else if (strcmp(lower, "mmd") == 0 || strcmp(lower, "multimarkdown") == 0) {
|
|
2466
|
+
*options = apex_options_for_mode(APEX_MODE_MULTIMARKDOWN);
|
|
2467
|
+
} else if (strcmp(lower, "kramdown") == 0) {
|
|
2468
|
+
*options = apex_options_for_mode(APEX_MODE_KRAMDOWN);
|
|
2469
|
+
} else if (strcmp(lower, "unified") == 0) {
|
|
2470
|
+
*options = apex_options_for_mode(APEX_MODE_UNIFIED);
|
|
2471
|
+
}
|
|
2472
|
+
free(lower);
|
|
2473
|
+
}
|
|
2474
|
+
break; /* Only process first mode found */
|
|
2475
|
+
}
|
|
2476
|
+
item = item->next;
|
|
2477
|
+
}
|
|
2478
|
+
|
|
2479
|
+
/* Second pass: apply all other metadata options */
|
|
2480
|
+
item = metadata;
|
|
2481
|
+
while (item) {
|
|
2482
|
+
const char *key = item->key;
|
|
2483
|
+
const char *value = item->value;
|
|
2484
|
+
|
|
2485
|
+
if (!key || !value) {
|
|
2486
|
+
item = item->next;
|
|
2487
|
+
continue;
|
|
2488
|
+
}
|
|
2489
|
+
|
|
2490
|
+
/* Skip mode - already processed */
|
|
2491
|
+
if (strcasecmp(key, "mode") == 0) {
|
|
2492
|
+
item = item->next;
|
|
2493
|
+
continue;
|
|
2494
|
+
}
|
|
2495
|
+
|
|
2496
|
+
/* Boolean flags (with --[no-] variants) */
|
|
2497
|
+
if (strcasecmp(key, "indices") == 0) {
|
|
2498
|
+
if (is_true_value(value)) {
|
|
2499
|
+
options->enable_indices = true;
|
|
2500
|
+
options->enable_mmark_index_syntax = true;
|
|
2501
|
+
options->enable_textindex_syntax = true;
|
|
2502
|
+
} else if (is_false_value(value)) {
|
|
2503
|
+
options->enable_indices = false;
|
|
2504
|
+
}
|
|
2505
|
+
} else if (strcasecmp(key, "wikilinks") == 0 || strcasecmp(key, "wiki-links") == 0) {
|
|
2506
|
+
if (is_true_value(value)) {
|
|
2507
|
+
options->enable_wiki_links = true;
|
|
2508
|
+
} else if (is_false_value(value)) {
|
|
2509
|
+
options->enable_wiki_links = false;
|
|
2510
|
+
}
|
|
2511
|
+
} else if (strcasecmp(key, "includes") == 0 || strcasecmp(key, "file-includes") == 0) {
|
|
2512
|
+
if (is_true_value(value)) {
|
|
2513
|
+
options->enable_file_includes = true;
|
|
2514
|
+
} else if (is_false_value(value)) {
|
|
2515
|
+
options->enable_file_includes = false;
|
|
2516
|
+
}
|
|
2517
|
+
} else if (strcasecmp(key, "relaxed-tables") == 0 || strcasecmp(key, "relaxed_tables") == 0) {
|
|
2518
|
+
if (is_true_value(value)) {
|
|
2519
|
+
options->relaxed_tables = true;
|
|
2520
|
+
} else if (is_false_value(value)) {
|
|
2521
|
+
options->relaxed_tables = false;
|
|
2522
|
+
}
|
|
2523
|
+
} else if (strcasecmp(key, "alpha-lists") == 0 || strcasecmp(key, "alpha_lists") == 0) {
|
|
2524
|
+
if (is_true_value(value)) {
|
|
2525
|
+
options->allow_alpha_lists = true;
|
|
2526
|
+
} else if (is_false_value(value)) {
|
|
2527
|
+
options->allow_alpha_lists = false;
|
|
2528
|
+
}
|
|
2529
|
+
} else if (strcasecmp(key, "mixed-lists") == 0 || strcasecmp(key, "mixed_lists") == 0) {
|
|
2530
|
+
if (is_true_value(value)) {
|
|
2531
|
+
options->allow_mixed_list_markers = true;
|
|
2532
|
+
} else if (is_false_value(value)) {
|
|
2533
|
+
options->allow_mixed_list_markers = false;
|
|
2534
|
+
}
|
|
2535
|
+
} else if (strcasecmp(key, "sup-sub") == 0 || strcasecmp(key, "sup_sub") == 0) {
|
|
2536
|
+
if (is_true_value(value)) {
|
|
2537
|
+
options->enable_sup_sub = true;
|
|
2538
|
+
} else if (is_false_value(value)) {
|
|
2539
|
+
options->enable_sup_sub = false;
|
|
2540
|
+
}
|
|
2541
|
+
} else if (strcasecmp(key, "autolink") == 0) {
|
|
2542
|
+
if (is_true_value(value)) {
|
|
2543
|
+
options->enable_autolink = true;
|
|
2544
|
+
} else if (is_false_value(value)) {
|
|
2545
|
+
options->enable_autolink = false;
|
|
2546
|
+
}
|
|
2547
|
+
} else if (strcasecmp(key, "strikethrough") == 0 || strcasecmp(key, "strike-through") == 0) {
|
|
2548
|
+
if (is_true_value(value)) {
|
|
2549
|
+
options->enable_strikethrough = true;
|
|
2550
|
+
} else if (is_false_value(value)) {
|
|
2551
|
+
options->enable_strikethrough = false;
|
|
2552
|
+
}
|
|
2553
|
+
} else if (strcasecmp(key, "transforms") == 0 || strcasecmp(key, "metadata-transforms") == 0) {
|
|
2554
|
+
if (is_true_value(value)) {
|
|
2555
|
+
options->enable_metadata_transforms = true;
|
|
2556
|
+
} else if (is_false_value(value)) {
|
|
2557
|
+
options->enable_metadata_transforms = false;
|
|
2558
|
+
}
|
|
2559
|
+
} else if (strcasecmp(key, "unsafe") == 0) {
|
|
2560
|
+
if (is_true_value(value)) {
|
|
2561
|
+
options->unsafe = true;
|
|
2562
|
+
} else if (is_false_value(value)) {
|
|
2563
|
+
options->unsafe = false;
|
|
2564
|
+
}
|
|
2565
|
+
} else if (strcasecmp(key, "plugins") == 0 || strcasecmp(key, "enable-plugins") == 0 || strcasecmp(key, "enable_plugins") == 0) {
|
|
2566
|
+
if (is_true_value(value)) {
|
|
2567
|
+
options->enable_plugins = true;
|
|
2568
|
+
} else if (is_false_value(value)) {
|
|
2569
|
+
options->enable_plugins = false;
|
|
2570
|
+
}
|
|
2571
|
+
} else if (strcasecmp(key, "tables") == 0) {
|
|
2572
|
+
if (is_true_value(value)) {
|
|
2573
|
+
options->enable_tables = true;
|
|
2574
|
+
} else if (is_false_value(value)) {
|
|
2575
|
+
options->enable_tables = false;
|
|
2576
|
+
}
|
|
2577
|
+
} else if (strcasecmp(key, "footnotes") == 0) {
|
|
2578
|
+
if (is_true_value(value)) {
|
|
2579
|
+
options->enable_footnotes = true;
|
|
2580
|
+
} else if (is_false_value(value)) {
|
|
2581
|
+
options->enable_footnotes = false;
|
|
2582
|
+
}
|
|
2583
|
+
} else if (strcasecmp(key, "smart") == 0 || strcasecmp(key, "smart-typography") == 0) {
|
|
2584
|
+
if (is_true_value(value)) {
|
|
2585
|
+
options->enable_smart_typography = true;
|
|
2586
|
+
} else if (is_false_value(value)) {
|
|
2587
|
+
options->enable_smart_typography = false;
|
|
2588
|
+
}
|
|
2589
|
+
} else if (strcasecmp(key, "math") == 0) {
|
|
2590
|
+
if (is_true_value(value)) {
|
|
2591
|
+
options->enable_math = true;
|
|
2592
|
+
} else if (is_false_value(value)) {
|
|
2593
|
+
options->enable_math = false;
|
|
2594
|
+
}
|
|
2595
|
+
} else if (strcasecmp(key, "ids") == 0 || strcasecmp(key, "header-ids") == 0) {
|
|
2596
|
+
if (is_true_value(value)) {
|
|
2597
|
+
options->generate_header_ids = true;
|
|
2598
|
+
} else if (is_false_value(value)) {
|
|
2599
|
+
options->generate_header_ids = false;
|
|
2600
|
+
}
|
|
2601
|
+
} else if (strcasecmp(key, "header-anchors") == 0 || strcasecmp(key, "header_anchors") == 0) {
|
|
2602
|
+
if (is_true_value(value)) {
|
|
2603
|
+
options->header_anchors = true;
|
|
2604
|
+
} else if (is_false_value(value)) {
|
|
2605
|
+
options->header_anchors = false;
|
|
2606
|
+
}
|
|
2607
|
+
} else if (strcasecmp(key, "embed-images") == 0 || strcasecmp(key, "embed_images") == 0) {
|
|
2608
|
+
if (is_true_value(value)) {
|
|
2609
|
+
options->embed_images = true;
|
|
2610
|
+
} else if (is_false_value(value)) {
|
|
2611
|
+
options->embed_images = false;
|
|
2612
|
+
}
|
|
2613
|
+
} else if (strcasecmp(key, "image-captions") == 0 || strcasecmp(key, "image_captions") == 0) {
|
|
2614
|
+
if (is_true_value(value)) {
|
|
2615
|
+
options->enable_image_captions = true;
|
|
2616
|
+
} else if (is_false_value(value)) {
|
|
2617
|
+
options->enable_image_captions = false;
|
|
2618
|
+
}
|
|
2619
|
+
} else if (strcasecmp(key, "title-captions-only") == 0 || strcasecmp(key, "title_captions_only") == 0) {
|
|
2620
|
+
if (is_true_value(value)) {
|
|
2621
|
+
options->title_captions_only = true;
|
|
2622
|
+
options->enable_image_captions = true; /* implied when title-captions-only is set */
|
|
2623
|
+
} else if (is_false_value(value)) {
|
|
2624
|
+
options->title_captions_only = false;
|
|
2625
|
+
}
|
|
2626
|
+
} else if (strcasecmp(key, "link-citations") == 0 || strcasecmp(key, "link_citations") == 0) {
|
|
2627
|
+
if (is_true_value(value)) {
|
|
2628
|
+
options->link_citations = true;
|
|
2629
|
+
} else if (is_false_value(value)) {
|
|
2630
|
+
options->link_citations = false;
|
|
2631
|
+
}
|
|
2632
|
+
} else if (strcasecmp(key, "show-tooltips") == 0 || strcasecmp(key, "show_tooltips") == 0) {
|
|
2633
|
+
if (is_true_value(value)) {
|
|
2634
|
+
options->show_tooltips = true;
|
|
2635
|
+
} else if (is_false_value(value)) {
|
|
2636
|
+
options->show_tooltips = false;
|
|
2637
|
+
}
|
|
2638
|
+
} else if (strcasecmp(key, "suppress-bibliography") == 0 || strcasecmp(key, "suppress_bibliography") == 0) {
|
|
2639
|
+
if (is_true_value(value)) {
|
|
2640
|
+
options->suppress_bibliography = true;
|
|
2641
|
+
} else if (is_false_value(value)) {
|
|
2642
|
+
options->suppress_bibliography = false;
|
|
2643
|
+
}
|
|
2644
|
+
} else if (strcasecmp(key, "suppress-index") == 0 || strcasecmp(key, "suppress_index") == 0) {
|
|
2645
|
+
if (is_true_value(value)) {
|
|
2646
|
+
options->suppress_index = true;
|
|
2647
|
+
} else if (is_false_value(value)) {
|
|
2648
|
+
options->suppress_index = false;
|
|
2649
|
+
}
|
|
2650
|
+
} else if (strcasecmp(key, "group-index-by-letter") == 0 || strcasecmp(key, "group_index_by_letter") == 0) {
|
|
2651
|
+
if (is_true_value(value)) {
|
|
2652
|
+
options->group_index_by_letter = true;
|
|
2653
|
+
} else if (is_false_value(value)) {
|
|
2654
|
+
options->group_index_by_letter = false;
|
|
2655
|
+
}
|
|
2656
|
+
} else if (strcasecmp(key, "obfuscate-emails") == 0 || strcasecmp(key, "obfuscate_emails") == 0) {
|
|
2657
|
+
if (is_true_value(value)) {
|
|
2658
|
+
options->obfuscate_emails = true;
|
|
2659
|
+
} else if (is_false_value(value)) {
|
|
2660
|
+
options->obfuscate_emails = false;
|
|
2661
|
+
}
|
|
2662
|
+
} else if (strcasecmp(key, "pretty") == 0) {
|
|
2663
|
+
if (is_true_value(value)) {
|
|
2664
|
+
options->pretty = true;
|
|
2665
|
+
} else if (is_false_value(value)) {
|
|
2666
|
+
options->pretty = false;
|
|
2667
|
+
}
|
|
2668
|
+
} else if (strcasecmp(key, "standalone") == 0) {
|
|
2669
|
+
if (is_true_value(value)) {
|
|
2670
|
+
options->standalone = true;
|
|
2671
|
+
} else if (is_false_value(value)) {
|
|
2672
|
+
options->standalone = false;
|
|
2673
|
+
}
|
|
2674
|
+
} else if (strcasecmp(key, "hardbreaks") == 0 || strcasecmp(key, "hard-breaks") == 0) {
|
|
2675
|
+
if (is_true_value(value)) {
|
|
2676
|
+
options->hardbreaks = true;
|
|
2677
|
+
} else if (is_false_value(value)) {
|
|
2678
|
+
options->hardbreaks = false;
|
|
2679
|
+
}
|
|
2680
|
+
} else if (strcasecmp(key, "widont") == 0) {
|
|
2681
|
+
if (is_true_value(value)) {
|
|
2682
|
+
options->enable_widont = true;
|
|
2683
|
+
} else if (is_false_value(value)) {
|
|
2684
|
+
options->enable_widont = false;
|
|
2685
|
+
}
|
|
2686
|
+
} else if (strcasecmp(key, "code-is-poetry") == 0 || strcasecmp(key, "code_is_poetry") == 0) {
|
|
2687
|
+
if (is_true_value(value)) {
|
|
2688
|
+
options->code_is_poetry = true;
|
|
2689
|
+
options->highlight_language_only = true;
|
|
2690
|
+
} else if (is_false_value(value)) {
|
|
2691
|
+
options->code_is_poetry = false;
|
|
2692
|
+
}
|
|
2693
|
+
} else if (strcasecmp(key, "markdown-in-html") == 0 || strcasecmp(key, "markdown_in_html") == 0) {
|
|
2694
|
+
if (is_true_value(value)) {
|
|
2695
|
+
options->enable_markdown_in_html = true;
|
|
2696
|
+
} else if (is_false_value(value)) {
|
|
2697
|
+
options->enable_markdown_in_html = false;
|
|
2698
|
+
}
|
|
2699
|
+
} else if (strcasecmp(key, "random-footnote-ids") == 0 || strcasecmp(key, "random_footnote_ids") == 0) {
|
|
2700
|
+
if (is_true_value(value)) {
|
|
2701
|
+
options->random_footnote_ids = true;
|
|
2702
|
+
} else if (is_false_value(value)) {
|
|
2703
|
+
options->random_footnote_ids = false;
|
|
2704
|
+
}
|
|
2705
|
+
} else if (strcasecmp(key, "hashtags") == 0) {
|
|
2706
|
+
if (is_true_value(value)) {
|
|
2707
|
+
options->enable_hashtags = true;
|
|
2708
|
+
} else if (is_false_value(value)) {
|
|
2709
|
+
options->enable_hashtags = false;
|
|
2710
|
+
}
|
|
2711
|
+
} else if (strcasecmp(key, "style-hashtags") == 0 || strcasecmp(key, "style_hashtags") == 0) {
|
|
2712
|
+
if (is_true_value(value)) {
|
|
2713
|
+
options->style_hashtags = true;
|
|
2714
|
+
} else if (is_false_value(value)) {
|
|
2715
|
+
options->style_hashtags = false;
|
|
2716
|
+
}
|
|
2717
|
+
} else if (strcasecmp(key, "proofreader") == 0) {
|
|
2718
|
+
if (is_true_value(value)) {
|
|
2719
|
+
options->proofreader_mode = true;
|
|
2720
|
+
options->enable_critic_markup = true;
|
|
2721
|
+
options->critic_mode = 2; /* markup mode */
|
|
2722
|
+
} else if (is_false_value(value)) {
|
|
2723
|
+
options->proofreader_mode = false;
|
|
2724
|
+
}
|
|
2725
|
+
} else if (strcasecmp(key, "hr-page-break") == 0 || strcasecmp(key, "hr_page_break") == 0) {
|
|
2726
|
+
if (is_true_value(value)) {
|
|
2727
|
+
options->hr_page_break = true;
|
|
2728
|
+
} else if (is_false_value(value)) {
|
|
2729
|
+
options->hr_page_break = false;
|
|
2730
|
+
}
|
|
2731
|
+
} else if (strcasecmp(key, "title-from-h1") == 0 || strcasecmp(key, "title_from_h1") == 0) {
|
|
2732
|
+
if (is_true_value(value)) {
|
|
2733
|
+
options->title_from_h1 = true;
|
|
2734
|
+
} else if (is_false_value(value)) {
|
|
2735
|
+
options->title_from_h1 = false;
|
|
2736
|
+
}
|
|
2737
|
+
} else if (strcasecmp(key, "page-break-before-footnotes") == 0 || strcasecmp(key, "page_break_before_footnotes") == 0) {
|
|
2738
|
+
if (is_true_value(value)) {
|
|
2739
|
+
options->page_break_before_footnotes = true;
|
|
2740
|
+
} else if (is_false_value(value)) {
|
|
2741
|
+
options->page_break_before_footnotes = false;
|
|
2742
|
+
}
|
|
2743
|
+
}
|
|
2744
|
+
/* String options */
|
|
2745
|
+
else if (strcasecmp(key, "bibliography") == 0) {
|
|
2746
|
+
/* Bibliography can be a single file or comma-separated list */
|
|
2747
|
+
/* Note: This will be handled by the citations extension, but we enable citations here */
|
|
2748
|
+
options->enable_citations = true;
|
|
2749
|
+
/* The actual bibliography file loading is handled in the citations extension */
|
|
2750
|
+
} else if (strcasecmp(key, "csl") == 0) {
|
|
2751
|
+
options->csl_file = value;
|
|
2752
|
+
options->enable_citations = true;
|
|
2753
|
+
} else if (strcasecmp(key, "title") == 0) {
|
|
2754
|
+
options->document_title = value;
|
|
2755
|
+
} else if (strcasecmp(key, "style") == 0 || strcasecmp(key, "css") == 0) {
|
|
2756
|
+
/* Metadata only supports single CSS file, so create array with one element */
|
|
2757
|
+
const char **css_array = malloc(2 * sizeof(const char*));
|
|
2758
|
+
if (css_array) {
|
|
2759
|
+
css_array[0] = value;
|
|
2760
|
+
css_array[1] = NULL;
|
|
2761
|
+
options->stylesheet_paths = css_array;
|
|
2762
|
+
options->stylesheet_count = 1;
|
|
2763
|
+
}
|
|
2764
|
+
options->standalone = true; /* Imply standalone if CSS is specified */
|
|
2765
|
+
} else if (strcasecmp(key, "id-format") == 0 || strcasecmp(key, "id_format") == 0) {
|
|
2766
|
+
/* Convert string to enum: gfm=0, mmd=1, kramdown=2 */
|
|
2767
|
+
char *lower = strdup(value);
|
|
2768
|
+
if (lower) {
|
|
2769
|
+
for (char *p = lower; *p; p++) {
|
|
2770
|
+
*p = (char)tolower((unsigned char)*p);
|
|
2771
|
+
}
|
|
2772
|
+
if (strcmp(lower, "gfm") == 0) {
|
|
2773
|
+
options->id_format = 0;
|
|
2774
|
+
} else if (strcmp(lower, "mmd") == 0) {
|
|
2775
|
+
options->id_format = 1;
|
|
2776
|
+
} else if (strcmp(lower, "kramdown") == 0) {
|
|
2777
|
+
options->id_format = 2;
|
|
2778
|
+
}
|
|
2779
|
+
free(lower);
|
|
2780
|
+
}
|
|
2781
|
+
} else if (strcasecmp(key, "base-dir") == 0 || strcasecmp(key, "base_dir") == 0) {
|
|
2782
|
+
options->base_directory = value;
|
|
2783
|
+
} else if (strcasecmp(key, "wikilink-space") == 0 || strcasecmp(key, "wikilink_space") == 0) {
|
|
2784
|
+
/* Convert string to enum: dash=0, none=1, underscore=2, space=3 */
|
|
2785
|
+
char *lower = strdup(value);
|
|
2786
|
+
if (lower) {
|
|
2787
|
+
for (char *p = lower; *p; p++) {
|
|
2788
|
+
*p = (char)tolower((unsigned char)*p);
|
|
2789
|
+
}
|
|
2790
|
+
if (strcmp(lower, "dash") == 0) {
|
|
2791
|
+
options->wikilink_space = 0;
|
|
2792
|
+
} else if (strcmp(lower, "none") == 0) {
|
|
2793
|
+
options->wikilink_space = 1;
|
|
2794
|
+
} else if (strcmp(lower, "underscore") == 0) {
|
|
2795
|
+
options->wikilink_space = 2;
|
|
2796
|
+
} else if (strcmp(lower, "space") == 0) {
|
|
2797
|
+
options->wikilink_space = 3;
|
|
2798
|
+
}
|
|
2799
|
+
free(lower);
|
|
2800
|
+
}
|
|
2801
|
+
} else if (strcasecmp(key, "wikilink-extension") == 0 || strcasecmp(key, "wikilink_extension") == 0) {
|
|
2802
|
+
options->wikilink_extension = value;
|
|
2803
|
+
} else if (strcasecmp(key, "wikilink-sanitize") == 0 || strcasecmp(key, "wikilink_sanitize") == 0) {
|
|
2804
|
+
options->wikilink_sanitize = (strcasecmp(value, "true") == 0 || strcmp(value, "1") == 0);
|
|
2805
|
+
}
|
|
2806
|
+
/* Syntax highlighting options */
|
|
2807
|
+
else if (strcasecmp(key, "code-highlight") == 0 || strcasecmp(key, "code_highlight") == 0) {
|
|
2808
|
+
/* Accept full names and abbreviations */
|
|
2809
|
+
char *lower = strdup(value);
|
|
2810
|
+
if (lower) {
|
|
2811
|
+
for (char *p = lower; *p; p++) {
|
|
2812
|
+
*p = (char)tolower((unsigned char)*p);
|
|
2813
|
+
}
|
|
2814
|
+
if (strcmp(lower, "pygments") == 0 || strcmp(lower, "p") == 0 || strcmp(lower, "pyg") == 0) {
|
|
2815
|
+
options->code_highlighter = "pygments";
|
|
2816
|
+
} else if (strcmp(lower, "skylighting") == 0 || strcmp(lower, "s") == 0 || strcmp(lower, "sky") == 0) {
|
|
2817
|
+
options->code_highlighter = "skylighting";
|
|
2818
|
+
} else if (is_false_value(lower) || strcmp(lower, "none") == 0) {
|
|
2819
|
+
options->code_highlighter = NULL;
|
|
2820
|
+
}
|
|
2821
|
+
free(lower);
|
|
2822
|
+
}
|
|
2823
|
+
} else if (strcasecmp(key, "code-line-numbers") == 0 || strcasecmp(key, "code_line_numbers") == 0) {
|
|
2824
|
+
if (is_true_value(value)) {
|
|
2825
|
+
options->code_line_numbers = true;
|
|
2826
|
+
} else if (is_false_value(value)) {
|
|
2827
|
+
options->code_line_numbers = false;
|
|
2828
|
+
}
|
|
2829
|
+
} else if (strcasecmp(key, "highlight-language-only") == 0 || strcasecmp(key, "highlight_language_only") == 0) {
|
|
2830
|
+
if (is_true_value(value)) {
|
|
2831
|
+
options->highlight_language_only = true;
|
|
2832
|
+
} else if (is_false_value(value)) {
|
|
2833
|
+
options->highlight_language_only = false;
|
|
2834
|
+
}
|
|
2835
|
+
}
|
|
2836
|
+
|
|
2837
|
+
item = item->next;
|
|
2838
|
+
}
|
|
2839
|
+
}
|
|
2840
|
+
|
|
2841
|
+
#ifdef APEX_HAVE_LIBYAML
|
|
2842
|
+
/* Convert a YAML mapping node to a flat metadata item list */
|
|
2843
|
+
static apex_metadata_item *yaml_mapping_to_metadata_items(yaml_document_t *doc, yaml_node_t *mapping_node) {
|
|
2844
|
+
if (!mapping_node || mapping_node->type != YAML_MAPPING_NODE) {
|
|
2845
|
+
return NULL;
|
|
2846
|
+
}
|
|
2847
|
+
|
|
2848
|
+
apex_metadata_item *items = NULL;
|
|
2849
|
+
yaml_node_pair_t *pair = mapping_node->data.mapping.pairs.start;
|
|
2850
|
+
while (pair < mapping_node->data.mapping.pairs.top) {
|
|
2851
|
+
yaml_node_t *key_node = yaml_document_get_node(doc, pair->key);
|
|
2852
|
+
yaml_node_t *value_node = yaml_document_get_node(doc, pair->value);
|
|
2853
|
+
|
|
2854
|
+
if (key_node && key_node->type == YAML_SCALAR_NODE && value_node) {
|
|
2855
|
+
char *key = (char *)key_node->data.scalar.value;
|
|
2856
|
+
size_t key_len = key_node->data.scalar.length;
|
|
2857
|
+
char *key_str = malloc(key_len + 1);
|
|
2858
|
+
if (key_str) {
|
|
2859
|
+
memcpy(key_str, key, key_len);
|
|
2860
|
+
key_str[key_len] = '\0';
|
|
2861
|
+
|
|
2862
|
+
if (value_node->type == YAML_SCALAR_NODE) {
|
|
2863
|
+
char *value = (char *)value_node->data.scalar.value;
|
|
2864
|
+
size_t value_len = value_node->data.scalar.length;
|
|
2865
|
+
char *value_str = malloc(value_len + 1);
|
|
2866
|
+
if (value_str) {
|
|
2867
|
+
memcpy(value_str, value, value_len);
|
|
2868
|
+
value_str[value_len] = '\0';
|
|
2869
|
+
add_metadata_item(&items, key_str, value_str);
|
|
2870
|
+
free(value_str);
|
|
2871
|
+
}
|
|
2872
|
+
} else if (value_node->type == YAML_MAPPING_NODE) {
|
|
2873
|
+
/* For nested mappings like handler: { command: "..." }, flatten with dot notation */
|
|
2874
|
+
yaml_node_pair_t *nested_pair = value_node->data.mapping.pairs.start;
|
|
2875
|
+
while (nested_pair < value_node->data.mapping.pairs.top) {
|
|
2876
|
+
yaml_node_t *nested_key = yaml_document_get_node(doc, nested_pair->key);
|
|
2877
|
+
yaml_node_t *nested_value = yaml_document_get_node(doc, nested_pair->value);
|
|
2878
|
+
if (nested_key && nested_key->type == YAML_SCALAR_NODE &&
|
|
2879
|
+
nested_value && nested_value->type == YAML_SCALAR_NODE) {
|
|
2880
|
+
char *nested_key_str = (char *)nested_key->data.scalar.value;
|
|
2881
|
+
size_t nested_key_len = nested_key->data.scalar.length;
|
|
2882
|
+
char *nested_value_str = (char *)nested_value->data.scalar.value;
|
|
2883
|
+
size_t nested_value_len = nested_value->data.scalar.length;
|
|
2884
|
+
|
|
2885
|
+
char *full_key = malloc(key_len + nested_key_len + 2);
|
|
2886
|
+
char *full_value = malloc(nested_value_len + 1);
|
|
2887
|
+
if (full_key && full_value) {
|
|
2888
|
+
memcpy(full_key, key_str, key_len);
|
|
2889
|
+
full_key[key_len] = '.';
|
|
2890
|
+
memcpy(full_key + key_len + 1, nested_key_str, nested_key_len);
|
|
2891
|
+
full_key[key_len + nested_key_len + 1] = '\0';
|
|
2892
|
+
memcpy(full_value, nested_value_str, nested_value_len);
|
|
2893
|
+
full_value[nested_value_len] = '\0';
|
|
2894
|
+
add_metadata_item(&items, full_key, full_value);
|
|
2895
|
+
}
|
|
2896
|
+
free(full_key);
|
|
2897
|
+
free(full_value);
|
|
2898
|
+
}
|
|
2899
|
+
nested_pair++;
|
|
2900
|
+
}
|
|
2901
|
+
}
|
|
2902
|
+
|
|
2903
|
+
free(key_str);
|
|
2904
|
+
}
|
|
2905
|
+
}
|
|
2906
|
+
pair++;
|
|
2907
|
+
}
|
|
2908
|
+
return items;
|
|
2909
|
+
}
|
|
2910
|
+
|
|
2911
|
+
yaml_document_t *apex_load_yaml_document(const char *filepath) {
|
|
2912
|
+
if (!filepath) return NULL;
|
|
2913
|
+
|
|
2914
|
+
FILE *fp = fopen(filepath, "r");
|
|
2915
|
+
if (!fp) return NULL;
|
|
2916
|
+
|
|
2917
|
+
fseek(fp, 0, SEEK_END);
|
|
2918
|
+
long file_size = ftell(fp);
|
|
2919
|
+
fseek(fp, 0, SEEK_SET);
|
|
2920
|
+
|
|
2921
|
+
if (file_size < 0 || file_size > 1024 * 1024) {
|
|
2922
|
+
fclose(fp);
|
|
2923
|
+
return NULL;
|
|
2924
|
+
}
|
|
2925
|
+
|
|
2926
|
+
char *buffer = malloc((size_t)file_size + 1);
|
|
2927
|
+
if (!buffer) {
|
|
2928
|
+
fclose(fp);
|
|
2929
|
+
return NULL;
|
|
2930
|
+
}
|
|
2931
|
+
|
|
2932
|
+
size_t bytes_read = fread(buffer, 1, (size_t)file_size, fp);
|
|
2933
|
+
buffer[bytes_read] = '\0';
|
|
2934
|
+
fclose(fp);
|
|
2935
|
+
|
|
2936
|
+
/* Skip YAML front matter markers if present */
|
|
2937
|
+
const char *yaml_start = buffer;
|
|
2938
|
+
size_t yaml_len = bytes_read;
|
|
2939
|
+
if (bytes_read >= 3 && strncmp(buffer, "---", 3) == 0) {
|
|
2940
|
+
const char *newline = strchr(buffer + 3, '\n');
|
|
2941
|
+
if (newline) {
|
|
2942
|
+
yaml_start = newline + 1;
|
|
2943
|
+
yaml_len = bytes_read - (yaml_start - buffer);
|
|
2944
|
+
} else {
|
|
2945
|
+
yaml_start = buffer + 3;
|
|
2946
|
+
yaml_len = bytes_read - 3;
|
|
2947
|
+
}
|
|
2948
|
+
|
|
2949
|
+
/* Find closing --- */
|
|
2950
|
+
const char *end_marker = strstr(yaml_start, "\n---");
|
|
2951
|
+
if (!end_marker) {
|
|
2952
|
+
end_marker = strstr(yaml_start, "\n...");
|
|
2953
|
+
}
|
|
2954
|
+
if (end_marker && end_marker > yaml_start) {
|
|
2955
|
+
yaml_len = (size_t)(end_marker - yaml_start);
|
|
2956
|
+
}
|
|
2957
|
+
}
|
|
2958
|
+
|
|
2959
|
+
yaml_parser_t parser;
|
|
2960
|
+
yaml_document_t *document = malloc(sizeof(yaml_document_t));
|
|
2961
|
+
if (!document) {
|
|
2962
|
+
free(buffer);
|
|
2963
|
+
return NULL;
|
|
2964
|
+
}
|
|
2965
|
+
|
|
2966
|
+
if (!yaml_parser_initialize(&parser)) {
|
|
2967
|
+
free(buffer);
|
|
2968
|
+
free(document);
|
|
2969
|
+
return NULL;
|
|
2970
|
+
}
|
|
2971
|
+
|
|
2972
|
+
yaml_parser_set_input_string(&parser, (const unsigned char *)yaml_start, yaml_len);
|
|
2973
|
+
|
|
2974
|
+
if (!yaml_parser_load(&parser, document)) {
|
|
2975
|
+
yaml_parser_delete(&parser);
|
|
2976
|
+
free(buffer);
|
|
2977
|
+
free(document);
|
|
2978
|
+
return NULL;
|
|
2979
|
+
}
|
|
2980
|
+
|
|
2981
|
+
yaml_parser_delete(&parser);
|
|
2982
|
+
free(buffer);
|
|
2983
|
+
return document;
|
|
2984
|
+
}
|
|
2985
|
+
|
|
2986
|
+
apex_metadata_item **apex_extract_plugin_bundle(const char *filepath, size_t *count) {
|
|
2987
|
+
*count = 0;
|
|
2988
|
+
if (!filepath) return NULL;
|
|
2989
|
+
|
|
2990
|
+
yaml_document_t *doc = apex_load_yaml_document(filepath);
|
|
2991
|
+
if (!doc) return NULL;
|
|
2992
|
+
|
|
2993
|
+
yaml_node_t *root = yaml_document_get_root_node(doc);
|
|
2994
|
+
if (!root || root->type != YAML_MAPPING_NODE) {
|
|
2995
|
+
yaml_document_delete(doc);
|
|
2996
|
+
free(doc);
|
|
2997
|
+
return NULL;
|
|
2998
|
+
}
|
|
2999
|
+
|
|
3000
|
+
/* Find "bundle" key */
|
|
3001
|
+
yaml_node_pair_t *pair = root->data.mapping.pairs.start;
|
|
3002
|
+
yaml_node_t *bundle_node = NULL;
|
|
3003
|
+
while (pair < root->data.mapping.pairs.top) {
|
|
3004
|
+
yaml_node_t *key_node = yaml_document_get_node(doc, pair->key);
|
|
3005
|
+
if (key_node && key_node->type == YAML_SCALAR_NODE) {
|
|
3006
|
+
char *key = (char *)key_node->data.scalar.value;
|
|
3007
|
+
if (strcmp(key, "bundle") == 0) {
|
|
3008
|
+
bundle_node = yaml_document_get_node(doc, pair->value);
|
|
3009
|
+
break;
|
|
3010
|
+
}
|
|
3011
|
+
}
|
|
3012
|
+
pair++;
|
|
3013
|
+
}
|
|
3014
|
+
|
|
3015
|
+
if (!bundle_node || bundle_node->type != YAML_SEQUENCE_NODE) {
|
|
3016
|
+
yaml_document_delete(doc);
|
|
3017
|
+
free(doc);
|
|
3018
|
+
return NULL;
|
|
3019
|
+
}
|
|
3020
|
+
|
|
3021
|
+
/* Extract each bundle entry */
|
|
3022
|
+
size_t bundle_count = bundle_node->data.sequence.items.top - bundle_node->data.sequence.items.start;
|
|
3023
|
+
apex_metadata_item **bundles = calloc(bundle_count, sizeof(apex_metadata_item *));
|
|
3024
|
+
if (!bundles) {
|
|
3025
|
+
yaml_document_delete(doc);
|
|
3026
|
+
free(doc);
|
|
3027
|
+
return NULL;
|
|
3028
|
+
}
|
|
3029
|
+
|
|
3030
|
+
int idx = 0;
|
|
3031
|
+
yaml_node_item_t *item = bundle_node->data.sequence.items.start;
|
|
3032
|
+
while (item < bundle_node->data.sequence.items.top) {
|
|
3033
|
+
yaml_node_t *entry = yaml_document_get_node(doc, *item);
|
|
3034
|
+
if (entry && entry->type == YAML_MAPPING_NODE) {
|
|
3035
|
+
bundles[idx] = yaml_mapping_to_metadata_items(doc, entry);
|
|
3036
|
+
idx++;
|
|
3037
|
+
}
|
|
3038
|
+
item++;
|
|
3039
|
+
}
|
|
3040
|
+
|
|
3041
|
+
*count = (size_t)idx;
|
|
3042
|
+
yaml_document_delete(doc);
|
|
3043
|
+
free(doc);
|
|
3044
|
+
return bundles;
|
|
3045
|
+
}
|
|
3046
|
+
#endif
|