apex-ruby 1.0.18 → 1.0.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (207) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +17 -3
  3. data/ext/apex_ext/apex_ext.c +260 -91
  4. data/ext/apex_ext/apex_src/CHANGELOG.md +440 -0
  5. data/ext/apex_ext/apex_src/CMakeLists.txt +15 -1
  6. data/ext/apex_ext/apex_src/Formula/apex.rb +7 -2
  7. data/ext/apex_ext/apex_src/Package.swift +15 -4
  8. data/ext/apex_ext/apex_src/README.md +94 -35
  9. data/ext/apex_ext/apex_src/VERSION +1 -1
  10. data/ext/apex_ext/apex_src/apex-debug.log +4 -0
  11. data/ext/apex_ext/apex_src/apex.code-workspace +38 -0
  12. data/ext/apex_ext/apex_src/apex.taskpaper +12 -0
  13. data/ext/apex_ext/apex_src/apex_deflist_debug.log +25 -0
  14. data/ext/apex_ext/apex_src/buildnotes.md +1435 -0
  15. data/ext/apex_ext/apex_src/cli/main.c +262 -33
  16. data/ext/apex_ext/apex_src/debug.log +7 -0
  17. data/ext/apex_ext/apex_src/docs/CITATIONS_EXAMPLES.md +274 -0
  18. data/ext/apex_ext/apex_src/docs/CITATIONS_IMPLEMENTATION_PLAN.md +749 -0
  19. data/ext/apex_ext/apex_src/docs/CITATIONS_PLAN.md +744 -0
  20. data/ext/apex_ext/apex_src/docs/INDEX_IMPLEMENTATION_ANALYSIS.md +382 -0
  21. data/ext/apex_ext/apex_src/docs/plans/2025-02-27-man-page-output-implementation.md +351 -0
  22. data/ext/apex_ext/apex_src/docs/superpowers/plans/2026-07-12-toc-output-format.md +745 -0
  23. data/ext/apex_ext/apex_src/docs/superpowers/plans/2026-07-18-bear-image-attributes.md +829 -0
  24. data/ext/apex_ext/apex_src/docs/superpowers/plans/2026-07-19-apex-node-npm.md +1148 -0
  25. data/ext/apex_ext/apex_src/docs/superpowers/specs/2026-07-12-toc-output-format-design.md +161 -0
  26. data/ext/apex_ext/apex_src/docs/superpowers/specs/2026-07-18-bear-image-attributes-design.md +184 -0
  27. data/ext/apex_ext/apex_src/docs/superpowers/specs/2026-07-19-apex-node-npm-design.md +211 -0
  28. data/ext/apex_ext/apex_src/documentation/app-settings-table.md +67 -0
  29. data/ext/apex_ext/apex_src/documentation/app-transformed/Citations.md +340 -0
  30. data/ext/apex_ext/apex_src/documentation/app-transformed/Credits.md +11 -0
  31. data/ext/apex_ext/apex_src/documentation/app-transformed/Header-IDs.md +234 -0
  32. data/ext/apex_ext/apex_src/documentation/app-transformed/Inline-Attribute-Lists.md +516 -0
  33. data/ext/apex_ext/apex_src/documentation/app-transformed/Metadata-Transforms.md +461 -0
  34. data/ext/apex_ext/apex_src/documentation/app-transformed/Modes.md +193 -0
  35. data/ext/apex_ext/apex_src/documentation/app-transformed/Multi-File-Documents.md +201 -0
  36. data/ext/apex_ext/apex_src/documentation/app-transformed/Plugins.md +678 -0
  37. data/ext/apex_ext/apex_src/documentation/app-transformed/Syntax.md +1367 -0
  38. data/ext/apex_ext/apex_src/documentation/docsets/Apex Command Line Options.cheatsheet.txt +6 -0
  39. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/C-API.html +774 -300
  40. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Callouts.html +1194 -0
  41. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Citations.html +416 -216
  42. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Command-Line-Options.html +3544 -1094
  43. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Configuration.html +371 -97
  44. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Credits.html +153 -23
  45. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Examples.html +358 -124
  46. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Filters.html +1656 -0
  47. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Generating-Man-Pages.html +1226 -0
  48. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Getting-Started.html +201 -56
  49. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Header-IDs.html +279 -109
  50. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Home.html +200 -53
  51. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Indices.html +2327 -0
  52. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Inline-Attribute-Lists.html +690 -271
  53. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Installation.html +201 -55
  54. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Limitations-and-Roadmap.html +238 -68
  55. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Metadata-Transforms.html +421 -221
  56. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Modes.html +340 -78
  57. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Multi-File-Documents.html +251 -86
  58. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Multi-Format-Images.html +1362 -0
  59. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Pandoc-Integration.html +277 -90
  60. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Plugins.html +673 -346
  61. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Quarto-Mode.html +1405 -0
  62. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Rendering-Markdown-In-Terminal.html +1902 -0
  63. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Syntax.html +1939 -1698
  64. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Tables.html +1781 -0
  65. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Troubleshooting.html +315 -140
  66. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Usage.html +307 -113
  67. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Using-Apex-with-Jekyll.html +1132 -0
  68. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Writing-Tests.html +1754 -0
  69. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/Documents/Xcode-Integration.html +872 -376
  70. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/docSet.dsidx +0 -0
  71. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/optimizedIndex.dsidx +0 -0
  72. data/ext/apex_ext/apex_src/documentation/html/apex-app-docs.html +8746 -0
  73. data/ext/apex_ext/apex_src/documentation/shared_styles.css +20 -15
  74. data/ext/apex_ext/apex_src/documentation/transform_for_app.md +55 -0
  75. data/ext/apex_ext/apex_src/escaping-repro.md +103 -0
  76. data/ext/apex_ext/apex_src/examples/quick-actions/Markdown to RTF.workflow/Contents/Info.plist +8 -0
  77. data/ext/apex_ext/apex_src/examples/quick-actions/Markdown to RTF.workflow/Contents/QuickLook/Preview.png +0 -0
  78. data/ext/apex_ext/apex_src/examples/quick-actions/Markdown to RTF.workflow/Contents/document.wflow +547 -0
  79. data/ext/apex_ext/apex_src/examples/quick-actions/Markdown-to-RTF.workflow.zip +0 -0
  80. data/ext/apex_ext/apex_src/examples/quick-actions/README.md +33 -0
  81. data/ext/apex_ext/apex_src/include/apex/apex.h +92 -6
  82. data/ext/apex_ext/apex_src/include/apex/ast_rtf.h +23 -0
  83. data/ext/apex_ext/apex_src/include/apex/ast_terminal.h +6 -0
  84. data/ext/apex_ext/apex_src/include/apex/module.modulemap +5 -0
  85. data/ext/apex_ext/apex_src/include/apex/plugins.h +105 -0
  86. data/ext/apex_ext/apex_src/include/apexc.h +1 -0
  87. data/ext/apex_ext/apex_src/man/apex.1 +82 -78
  88. data/ext/apex_ext/apex_src/man/apex.1.md +81 -4
  89. data/ext/apex_ext/apex_src/objc/Apex.swift +73 -0
  90. data/ext/apex_ext/apex_src/objc/ApexPlugins.swift +79 -0
  91. data/ext/apex_ext/apex_src/objc/NSString+Apex.h +83 -5
  92. data/ext/apex_ext/apex_src/objc/NSString+Apex.m +244 -1
  93. data/ext/apex_ext/apex_src/objc/NSString+ApexPlugins.h +76 -0
  94. data/ext/apex_ext/apex_src/objc/NSString+ApexPlugins.m +169 -0
  95. data/ext/apex_ext/apex_src/output.html +87 -0
  96. data/ext/apex_ext/apex_src/plans/quarto-mode.md +442 -0
  97. data/ext/apex_ext/apex_src/src/_README.md +85 -32
  98. data/ext/apex_ext/apex_src/src/apex.c +770 -200
  99. data/ext/apex_ext/apex_src/src/ast_rtf.c +848 -0
  100. data/ext/apex_ext/apex_src/src/ast_terminal.c +184 -7
  101. data/ext/apex_ext/apex_src/src/extensions/abbreviations.c +10 -3
  102. data/ext/apex_ext/apex_src/src/extensions/advanced_tables.c +7 -11
  103. data/ext/apex_ext/apex_src/src/extensions/bear_image_attrs.c +467 -0
  104. data/ext/apex_ext/apex_src/src/extensions/bear_image_attrs.h +27 -0
  105. data/ext/apex_ext/apex_src/src/extensions/callouts.c +400 -4
  106. data/ext/apex_ext/apex_src/src/extensions/callouts.h +15 -5
  107. data/ext/apex_ext/apex_src/src/extensions/citations.c +26 -5
  108. data/ext/apex_ext/apex_src/src/extensions/code_fence_attrs.c +632 -0
  109. data/ext/apex_ext/apex_src/src/extensions/code_fence_attrs.h +22 -0
  110. data/ext/apex_ext/apex_src/src/extensions/concordance.c +632 -0
  111. data/ext/apex_ext/apex_src/src/extensions/definition_list.c +129 -15
  112. data/ext/apex_ext/apex_src/src/extensions/grid_tables.c +1050 -121
  113. data/ext/apex_ext/apex_src/src/extensions/html_markdown.c +569 -16
  114. data/ext/apex_ext/apex_src/src/extensions/ial.c +502 -52
  115. data/ext/apex_ext/apex_src/src/extensions/ial.h +2 -1
  116. data/ext/apex_ext/apex_src/src/extensions/includes.c +532 -33
  117. data/ext/apex_ext/apex_src/src/extensions/includes.h +7 -1
  118. data/ext/apex_ext/apex_src/src/extensions/index.c +1427 -140
  119. data/ext/apex_ext/apex_src/src/extensions/index.h +28 -0
  120. data/ext/apex_ext/apex_src/src/extensions/metadata.c +200 -161
  121. data/ext/apex_ext/apex_src/src/extensions/quarto_diagrams.c +251 -0
  122. data/ext/apex_ext/apex_src/src/extensions/quarto_diagrams.h +22 -0
  123. data/ext/apex_ext/apex_src/src/extensions/quarto_lists.c +624 -0
  124. data/ext/apex_ext/apex_src/src/extensions/quarto_lists.h +35 -0
  125. data/ext/apex_ext/apex_src/src/extensions/quarto_polish.c +264 -0
  126. data/ext/apex_ext/apex_src/src/extensions/quarto_polish.h +23 -0
  127. data/ext/apex_ext/apex_src/src/extensions/quarto_shortcodes.c +307 -0
  128. data/ext/apex_ext/apex_src/src/extensions/quarto_shortcodes.h +21 -0
  129. data/ext/apex_ext/apex_src/src/extensions/raw_content.c +351 -0
  130. data/ext/apex_ext/apex_src/src/extensions/raw_content.h +20 -0
  131. data/ext/apex_ext/apex_src/src/extensions/relaxed_tables.c +117 -3
  132. data/ext/apex_ext/apex_src/src/extensions/special_markers.c +53 -0
  133. data/ext/apex_ext/apex_src/src/extensions/syntax_highlight.c +23 -1
  134. data/ext/apex_ext/apex_src/src/extensions/table_html_postprocess.c +83 -92
  135. data/ext/apex_ext/apex_src/src/extensions/toc.c +446 -25
  136. data/ext/apex_ext/apex_src/src/extensions/toc.h +26 -1
  137. data/ext/apex_ext/apex_src/src/extensions/wiki_links.c +28 -0
  138. data/ext/apex_ext/apex_src/src/html_renderer.c +387 -17
  139. data/ext/apex_ext/apex_src/src/plugin_catalog.c +517 -0
  140. data/ext/apex_ext/apex_src/src/plugins_remote.c +88 -14
  141. data/ext/apex_ext/apex_src/src/plugins_remote.h +34 -0
  142. data/ext/apex_ext/apex_src/src/pretty_html.c +6 -1
  143. data/ext/apex_ext/apex_src/tests/fixtures/comprehensive_test.md +21 -0
  144. data/ext/apex_ext/apex_src/tests/fixtures/images/bear_image_attributes.md +10 -0
  145. data/ext/apex_ext/apex_src/tests/fixtures/includes/sections.md +19 -0
  146. data/ext/apex_ext/apex_src/tests/fixtures/includes/sections.txt +11 -0
  147. data/ext/apex_ext/apex_src/tests/fixtures/minimal_pb.md +2 -0
  148. data/ext/apex_ext/apex_src/tests/fixtures/obsidian-callouts.md +46 -0
  149. data/ext/apex_ext/apex_src/tests/fixtures/pagebreak_html_repro.md +5 -0
  150. data/ext/apex_ext/apex_src/tests/fixtures/python-callouts.md +47 -0
  151. data/ext/apex_ext/apex_src/tests/fixtures/quarto/callouts.md +3 -0
  152. data/ext/apex_ext/apex_src/tests/fixtures/quarto/code-attrs.md +3 -0
  153. data/ext/apex_ext/apex_src/tests/fixtures/quarto/diagrams.md +8 -0
  154. data/ext/apex_ext/apex_src/tests/fixtures/quarto/lists.md +19 -0
  155. data/ext/apex_ext/apex_src/tests/fixtures/quarto/raw-content.md +5 -0
  156. data/ext/apex_ext/apex_src/tests/fixtures/quarto/shortcodes.md +7 -0
  157. data/ext/apex_ext/apex_src/tests/fixtures/quarto/smoke.md +81 -0
  158. data/ext/apex_ext/apex_src/tests/fixtures/quarto/spans.md +5 -0
  159. data/ext/apex_ext/apex_src/tests/fixtures/quarto-callouts.md +47 -0
  160. data/ext/apex_ext/apex_src/tests/fixtures/speed.md +192 -0
  161. data/ext/apex_ext/apex_src/tests/fixtures/tables/grid-tables.md +37 -0
  162. data/ext/apex_ext/apex_src/tests/paginate_cli_test.sh +16 -0
  163. data/ext/apex_ext/apex_src/tests/test_bear_image_attrs.c +301 -0
  164. data/ext/apex_ext/apex_src/tests/test_concordance.tsv +4 -0
  165. data/ext/apex_ext/apex_src/tests/test_escaping_repro.c +82 -0
  166. data/ext/apex_ext/apex_src/tests/test_extensions.c +821 -1
  167. data/ext/apex_ext/apex_src/tests/test_ial.c +7 -0
  168. data/ext/apex_ext/apex_src/tests/test_marked_integration.c +25 -0
  169. data/ext/apex_ext/apex_src/tests/test_metadata.c +129 -0
  170. data/ext/apex_ext/apex_src/tests/test_output.c +568 -2
  171. data/ext/apex_ext/apex_src/tests/test_runner.c +11 -0
  172. data/ext/apex_ext/apex_src/tests/test_tables.c +420 -0
  173. data/ext/apex_ext/apex_src/vendor/cmark-gfm/CMakeLists.txt +1 -1
  174. data/ext/apex_ext/apex_src/vendor/cmark-gfm/extensions/autolink.c +77 -1
  175. data/ext/apex_ext/apex_src/vendor/cmark-gfm/extensions/table.c +135 -5
  176. data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/blocks.c +56 -1
  177. data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/config.h +38 -0
  178. data/ext/apex_ext/apex_src/vendor/cmark-gfm/src/main.c +1 -1
  179. data/ext/apex_ext/apex_src/vendor/cmark-gfm/test/__pycache__/cmark.cpython-314.pyc +0 -0
  180. data/ext/apex_ext/apex_src/vendor/cmark-gfm/test/__pycache__/normalize.cpython-314.pyc +0 -0
  181. data/ext/apex_ext/apex_src/vendor/cmark-gfm/test/extensions.txt +69 -0
  182. data/ext/apex_ext/apex_src/xcode_build/CMakeScripts/ReRunCMake.make +74 -0
  183. data/ext/apex_ext/apex_src/xcode_build/CTestTestfile.cmake +22 -0
  184. data/ext/apex_ext/apex_src/xcode_build/Debug/Apex.framework/Versions/A/Apex +0 -0
  185. data/ext/apex_ext/apex_src/xcode_build/Debug/Apex.framework/Versions/A/Resources/Info.plist +44 -0
  186. data/ext/apex_ext/apex_src/xcode_build/Debug/Apex.framework/Versions/A/_CodeSignature/CodeResources +128 -0
  187. data/ext/apex_ext/apex_src/xcode_build/apex.xcodeproj/project.pbxproj +5118 -0
  188. data/ext/apex_ext/apex_src/xcode_build/apex.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +8 -0
  189. data/ext/apex_ext/apex_src/xcode_build/apex.xcodeproj/project.xcworkspace/xcuserdata/ttscoff.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  190. data/ext/apex_ext/apex_src/xcode_build/apex.xcodeproj/project.xcworkspace/xcuserdata/ttscoff.xcuserdatad/WorkspaceSettings.xcsettings +8 -0
  191. data/ext/apex_ext/apex_src/xcode_build/apex.xcodeproj/xcuserdata/ttscoff.xcuserdatad/xcschemes/xcschememanagement.plist +99 -0
  192. data/ext/apex_ext/apex_src/xcode_build/cmake_install.cmake +452 -0
  193. data/ext/apex_ext/apex_src/xcode_build/vendor/cmark-gfm/CMakeScripts/ReRunCMake.make +52 -0
  194. data/ext/apex_ext/apex_src/xcode_build/vendor/cmark-gfm/cmake_install.cmake +55 -0
  195. data/ext/apex_ext/apex_src/xcode_build/vendor/cmark-gfm/cmark-gfm.xcodeproj/project.pbxproj +2455 -0
  196. data/ext/apex_ext/apex_src/xcode_build/vendor/cmark-gfm/cmark-gfm.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +8 -0
  197. data/ext/apex_ext/apex_src/xcode_build/vendor/cmark-gfm/extensions/cmake_install.cmake +171 -0
  198. data/ext/apex_ext/apex_src/xcode_build/vendor/cmark-gfm/man/cmake_install.cmake +55 -0
  199. data/ext/apex_ext/apex_src/xcode_build/vendor/cmark-gfm/src/cmake_install.cmake +220 -0
  200. data/ext/apex_ext/apex_src/xcode_build/vendor/cmark-gfm/src/cmark-gfm_export.h +43 -0
  201. data/ext/apex_ext/apex_src/xcode_build/vendor/cmark-gfm/src/cmark-gfm_version.h +7 -0
  202. data/ext/apex_ext/apex_src/xcode_build/vendor/cmark-gfm/src/config.h +76 -0
  203. data/ext/apex_ext/apex_src/xcode_build/vendor/cmark-gfm/src/libcmark-gfm.pc +10 -0
  204. data/ext/apex_ext/extconf.rb +35 -0
  205. data/lib/apex/version.rb +2 -2
  206. metadata +117 -2
  207. data/ext/apex_ext/apex_src/documentation/docsets/Apex.docset/Contents/Resources/tempOptimizedIndex.dsidx +0 -0
@@ -16,18 +16,24 @@
16
16
  #define MAX_COLUMNS 64
17
17
 
18
18
  /**
19
- * Check if a line starts with '+' (after whitespace)
20
- * This indicates a potential grid table
19
+ * Check if a line starts a grid table block (separator row: +---+ or +===+).
20
+ * Bare lines starting with '+' alone are not grid tables.
21
21
  */
22
22
  static bool is_grid_table_start(const char *line) {
23
23
  if (!line) return false;
24
24
 
25
- /* Skip whitespace */
26
25
  while (*line == ' ' || *line == '\t') {
27
26
  line++;
28
27
  }
29
28
 
30
- return *line == '+';
29
+ if (*line != '+') return false;
30
+ line++;
31
+
32
+ while (*line == ' ' || *line == '\t') {
33
+ line++;
34
+ }
35
+
36
+ return *line == '-' || *line == '=';
31
37
  }
32
38
 
33
39
  /**
@@ -97,6 +103,24 @@ static bool is_grid_table_separator(const char *line) {
97
103
  return has_dash_or_equal;
98
104
  }
99
105
 
106
+ static bool grid_block_has_separator(char **lines, size_t line_count) {
107
+ for (size_t i = 0; i < line_count; i++) {
108
+ if (lines[i] && is_grid_table_separator(lines[i])) {
109
+ return true;
110
+ }
111
+ }
112
+ return false;
113
+ }
114
+
115
+ static bool grid_block_has_content_rows(char **lines, size_t line_count) {
116
+ for (size_t i = 0; i < line_count; i++) {
117
+ if (lines[i] && strchr(lines[i], '|') && !is_grid_table_separator(lines[i])) {
118
+ return true;
119
+ }
120
+ }
121
+ return false;
122
+ }
123
+
100
124
  /**
101
125
  * Check if a line is a header separator (contains = characters)
102
126
  */
@@ -395,6 +419,28 @@ static bool has_same_column_structure(const char *line1, const char *line2) {
395
419
  return pipe_count1 > 0 && pipe_count1 == pipe_count2;
396
420
  }
397
421
 
422
+ static char *str_append(char *dest, const char *add);
423
+
424
+ /**
425
+ * Append a line to a string with newline separator
426
+ */
427
+ static char *str_append_line(char *dest, const char *line) {
428
+ if (!line) return dest;
429
+ if (!dest) return str_append(NULL, line);
430
+ dest = str_append(dest, "\n");
431
+ return str_append(dest, line);
432
+ }
433
+
434
+ static char *str_append(char *dest, const char *add) {
435
+ if (!add) return dest;
436
+ size_t old_len = dest ? strlen(dest) : 0;
437
+ size_t add_len = strlen(add);
438
+ char *new_str = realloc(dest, old_len + add_len + 1);
439
+ if (!new_str) return dest;
440
+ memcpy(new_str + old_len, add, add_len + 1);
441
+ return new_str;
442
+ }
443
+
398
444
  /**
399
445
  * Extract cell content from grid table row lines
400
446
  * Grid table rows can span multiple lines until the next separator
@@ -415,6 +461,13 @@ static char **extract_cells_from_row_lines(char **row_lines, size_t row_line_cou
415
461
 
416
462
  /* Skip separator lines - they shouldn't be in row_lines, but double-check */
417
463
  if (is_grid_table_separator(line)) {
464
+ /* Nested grid separator inside colspan cell: preserve as literal content */
465
+ if (*cell_count > 0) {
466
+ size_t idx = (*cell_count > 0) ? 0 : 0;
467
+ if (cells[idx]) {
468
+ cells[idx] = str_append_line(cells[idx], line);
469
+ }
470
+ }
418
471
  continue;
419
472
  }
420
473
 
@@ -582,12 +635,491 @@ static char *create_pipe_separator(int *alignments, size_t column_count) {
582
635
  return sep;
583
636
  }
584
637
 
638
+ static bool is_nested_grid_separator(const char *line, size_t table_cols) {
639
+ if (!line || !is_grid_table_separator(line)) return false;
640
+ if (is_header_separator(line)) return false;
641
+ size_t sep_cols = 0;
642
+ int tmp[MAX_COLUMNS] = {0};
643
+ parse_alignment(line, tmp, &sep_cols);
644
+ return sep_cols > table_cols;
645
+ }
646
+
647
+ /**
648
+ * Check if table contains partial separators (|...+...+...) that require HTML output.
649
+ */
650
+ static bool table_has_nested_grid(char **lines, size_t line_count, size_t table_cols) {
651
+ if (table_cols == 0) return false;
652
+ for (size_t i = 0; i < line_count; i++) {
653
+ if (lines[i] && is_nested_grid_separator(lines[i], table_cols)) return true;
654
+ }
655
+ return false;
656
+ }
657
+
658
+ static bool table_has_partial_separators(char **lines, size_t line_count) {
659
+ for (size_t i = 0; i < line_count; i++) {
660
+ if (!lines[i]) continue;
661
+ const char *p = lines[i];
662
+ while (*p == ' ' || *p == '\t') p++;
663
+ if (*p != '+' && *p != '|') continue;
664
+ if (is_grid_table_separator(lines[i])) continue;
665
+ /* Mixed row: starts with | and contains internal +---+ pattern */
666
+ if (*p == '|') {
667
+ bool seen_pipe = false;
668
+ for (const char *q = p; *q && *q != '\n'; q++) {
669
+ if (*q == '|') seen_pipe = true;
670
+ if (seen_pipe && *q == '+' && q[1] == '-') return true;
671
+ }
672
+ }
673
+ }
674
+ return false;
675
+ }
676
+
677
+ static bool table_has_multiline_cells(char **lines, size_t line_count) {
678
+ for (size_t i = 0; i + 1 < line_count; i++) {
679
+ if (!lines[i] || !lines[i + 1]) continue;
680
+ if (is_grid_table_separator(lines[i]) || is_grid_table_separator(lines[i + 1])) continue;
681
+ if (strchr(lines[i], '|') && strchr(lines[i + 1], '|') &&
682
+ has_same_column_structure(lines[i], lines[i + 1])) {
683
+ return true;
684
+ }
685
+ }
686
+ return false;
687
+ }
688
+
689
+ static bool is_full_row_colspan_line(const char *line) {
690
+ return line && count_cells_in_line(line) == 1;
691
+ }
692
+
585
693
  /**
586
- * Convert grid table rows to pipe table format
587
- * Groups lines between separators into rows and handles multi-line cells
694
+ * Parse | c0 | c1 | c2 | ... into cell strings (trimmed). Returns cell count.
588
695
  */
696
+ static size_t parse_grid_row_cells(const char *line, char **cells, size_t max_cells) {
697
+ if (!line || !cells || max_cells == 0) return 0;
698
+
699
+ const char *p = line;
700
+ while (*p == ' ' || *p == '\t') p++;
701
+ if (*p == '+') return 0;
702
+
703
+ size_t count = 0;
704
+ const char *cell_start = NULL;
705
+ bool first_pipe = true;
706
+
707
+ while (*p && count < max_cells) {
708
+ if (*p == '|') {
709
+ if (cell_start) {
710
+ const char *cell_end = p;
711
+ while (cell_end > cell_start && isspace((unsigned char)cell_end[-1])) cell_end--;
712
+ size_t len = (size_t)(cell_end - cell_start);
713
+ cells[count] = malloc(len + 1);
714
+ if (cells[count]) {
715
+ memcpy(cells[count], cell_start, len);
716
+ cells[count][len] = '\0';
717
+ }
718
+ count++;
719
+ } else if (!first_pipe) {
720
+ cells[count] = strdup("");
721
+ if (cells[count]) count++;
722
+ }
723
+ first_pipe = false;
724
+ cell_start = p + 1;
725
+ }
726
+ p++;
727
+ }
728
+
729
+ if (cell_start && count < max_cells) {
730
+ const char *cell_end = p;
731
+ while (cell_end > cell_start && isspace((unsigned char)cell_end[-1])) cell_end--;
732
+ if (cell_end > cell_start) {
733
+ size_t len = (size_t)(cell_end - cell_start);
734
+ cells[count] = malloc(len + 1);
735
+ if (cells[count]) {
736
+ memcpy(cells[count], cell_start, len);
737
+ cells[count][len] = '\0';
738
+ count++;
739
+ }
740
+ }
741
+ }
742
+
743
+ return count;
744
+ }
745
+
746
+ static bool line_is_partial_col0_separator(const char *line) {
747
+ if (!line) return false;
748
+ const char *p = line;
749
+ while (*p == ' ' || *p == '\t') p++;
750
+ if (*p != '|') return false;
751
+ p++;
752
+ while (*p == ' ' || *p == '\t') p++;
753
+ if (*p == '+' && p[1] == '-') return true;
754
+ return false;
755
+ }
756
+
757
+ static char *extract_partial_col0_text(const char *line) {
758
+ if (!line) return NULL;
759
+ const char *p = line;
760
+ while (*p == ' ' || *p == '\t') p++;
761
+ if (*p != '|') return NULL;
762
+ p++;
763
+ while (*p == ' ' || *p == '\t') p++;
764
+
765
+ const char *start = p;
766
+ const char *plus = NULL;
767
+ for (const char *q = p; *q && *q != '\n'; q++) {
768
+ if (*q == '+' && q[1] == '-') {
769
+ plus = q;
770
+ break;
771
+ }
772
+ }
773
+ if (!plus) return NULL;
774
+
775
+ const char *end = plus;
776
+ while (end > start && isspace((unsigned char)end[-1])) end--;
777
+ if (end <= start) return NULL;
778
+
779
+ size_t len = (size_t)(end - start);
780
+ char *out = malloc(len + 1);
781
+ if (!out) return NULL;
782
+ memcpy(out, start, len);
783
+ out[len] = '\0';
784
+ return out;
785
+ }
786
+
787
+ static char *append_col0_label(char *label, const char *add) {
788
+ if (!add || !*add) return label;
789
+ if (!label) return strdup(add);
790
+ size_t old = strlen(label);
791
+ size_t add_len = strlen(add);
792
+ char *out = realloc(label, old + add_len + 2);
793
+ if (!out) return label;
794
+ if (old > 0) {
795
+ out[old] = ' ';
796
+ out[old + 1] = '\0';
797
+ old++;
798
+ }
799
+ strcat(out, add);
800
+ return out;
801
+ }
802
+
803
+ static void write_html_cell(char **p, size_t *remaining, char **output, size_t *total_size,
804
+ const char *tag, const char *content, size_t colspan, size_t rowspan,
805
+ bool markdown) {
806
+ char open[256];
807
+ if (colspan > 1 && rowspan > 1) {
808
+ snprintf(open, sizeof(open), "<%s%s colspan=\"%zu\" rowspan=\"%zu\">\n\n",
809
+ tag, markdown ? " markdown=\"1\"" : "", colspan, rowspan);
810
+ } else if (colspan > 1) {
811
+ snprintf(open, sizeof(open), "<%s%s colspan=\"%zu\">\n\n",
812
+ tag, markdown ? " markdown=\"1\"" : "", colspan);
813
+ } else if (rowspan > 1) {
814
+ snprintf(open, sizeof(open), "<%s%s rowspan=\"%zu\">\n\n",
815
+ tag, markdown ? " markdown=\"1\"" : "", rowspan);
816
+ } else {
817
+ snprintf(open, sizeof(open), "<%s%s>\n\n", tag, markdown ? " markdown=\"1\"" : "");
818
+ }
819
+
820
+ size_t need = strlen(open) + (content ? strlen(content) : 0) + 32;
821
+ char *base = *output;
822
+ size_t used = (size_t)(*p - base);
823
+ if (*remaining < need) {
824
+ *total_size = (used + need + 1) * 2;
825
+ char *grown = realloc(base, *total_size);
826
+ if (!grown) return;
827
+ *output = grown;
828
+ *p = grown + used;
829
+ *remaining = *total_size - used;
830
+ }
831
+
832
+ size_t ol = strlen(open);
833
+ memcpy(*p, open, ol);
834
+ *p += ol;
835
+ *remaining -= ol;
836
+
837
+ if (content && *content) {
838
+ size_t cl = strlen(content);
839
+ memcpy(*p, content, cl);
840
+ *p += cl;
841
+ *remaining -= cl;
842
+ }
843
+
844
+ char close[16];
845
+ snprintf(close, sizeof(close), "\n\n</%s>\n", tag);
846
+ size_t cll = strlen(close);
847
+ memcpy(*p, close, cll);
848
+ *p += cll;
849
+ *remaining -= cll;
850
+ }
851
+
852
+ /**
853
+ * Convert grid tables with in-row partial separators (Pandoc-style).
854
+ */
855
+ static char *convert_partial_grid_table_to_html(char **lines, size_t line_count,
856
+ size_t column_count) {
857
+ if (!lines || line_count == 0 || column_count == 0) return NULL;
858
+
859
+ size_t header_sep_idx = line_count;
860
+ for (size_t i = 0; i < line_count; i++) {
861
+ if (lines[i] && is_header_separator(lines[i])) {
862
+ header_sep_idx = i;
863
+ break;
864
+ }
865
+ }
866
+
867
+ size_t total_size = 4096;
868
+ char *output = malloc(total_size);
869
+ if (!output) return NULL;
870
+ char *p = output;
871
+ size_t remaining = total_size;
872
+
873
+ memcpy(p, "\n\n<table>\n", 10);
874
+ p += 10;
875
+ remaining -= 10;
876
+
877
+ /* Header rows before +===+ separator */
878
+ for (size_t i = 0; i < header_sep_idx; i++) {
879
+ if (!lines[i] || is_grid_table_separator(lines[i]) || !strchr(lines[i], '|')) continue;
880
+
881
+ char *cells[MAX_COLUMNS] = {0};
882
+ size_t ncells = parse_grid_row_cells(lines[i], cells, MAX_COLUMNS);
883
+ if (ncells == 0) continue;
884
+
885
+ const char *thead = "<thead>\n<tr>\n";
886
+ size_t tl = strlen(thead);
887
+ memcpy(p, thead, tl);
888
+ p += tl;
889
+ remaining -= tl;
890
+
891
+ size_t first_span = (ncells < column_count) ? (column_count - ncells + 1) : 1;
892
+ for (size_t c = 0; c < ncells; c++) {
893
+ size_t span = (ncells < column_count && c == 0) ? first_span : 1;
894
+ write_html_cell(&p, &remaining, &output, &total_size, "th", cells[c], span, 1, true);
895
+ free(cells[c]);
896
+ }
897
+
898
+ const char *end = "</tr>\n</thead>\n<tbody>\n";
899
+ size_t el = strlen(end);
900
+ memcpy(p, end, el);
901
+ p += el;
902
+ remaining -= el;
903
+ break;
904
+ }
905
+
906
+ if (header_sep_idx >= line_count) {
907
+ const char *tbody = "<tbody>\n";
908
+ size_t bl = strlen(tbody);
909
+ memcpy(p, tbody, bl);
910
+ p += bl;
911
+ remaining -= bl;
912
+ }
913
+
914
+ /* Body rows after header separator */
915
+ char *col0_label = NULL;
916
+ char **row_c1 = NULL;
917
+ char **row_c2 = NULL;
918
+ size_t row_count = 0;
919
+ size_t row_cap = 8;
920
+
921
+ row_c1 = malloc(row_cap * sizeof(char*));
922
+ row_c2 = malloc(row_cap * sizeof(char*));
923
+ if (!row_c1 || !row_c2) {
924
+ free(col0_label);
925
+ free(row_c1);
926
+ free(row_c2);
927
+ free(output);
928
+ return NULL;
929
+ }
930
+
931
+ for (size_t i = header_sep_idx + 1; i < line_count; i++) {
932
+ if (!lines[i] || is_grid_table_separator(lines[i])) continue;
933
+
934
+ if (line_is_partial_col0_separator(lines[i])) continue;
935
+
936
+ char *partial = extract_partial_col0_text(lines[i]);
937
+ if (partial) {
938
+ col0_label = append_col0_label(col0_label, partial);
939
+ free(partial);
940
+ continue;
941
+ }
942
+
943
+ char *cells[MAX_COLUMNS] = {0};
944
+ size_t ncells = parse_grid_row_cells(lines[i], cells, column_count);
945
+ if (ncells == 0) {
946
+ continue;
947
+ }
948
+
949
+ if (ncells >= 1 && cells[0] && *cells[0]) {
950
+ col0_label = append_col0_label(col0_label, cells[0]);
951
+ }
952
+
953
+ if (ncells >= 3) {
954
+ if (row_count >= row_cap) {
955
+ row_cap *= 2;
956
+ row_c1 = realloc(row_c1, row_cap * sizeof(char*));
957
+ row_c2 = realloc(row_c2, row_cap * sizeof(char*));
958
+ }
959
+ row_c1[row_count] = cells[1] ? strdup(cells[1]) : strdup("");
960
+ row_c2[row_count] = cells[2] ? strdup(cells[2]) : strdup("");
961
+ free(cells[0]);
962
+ free(cells[1]);
963
+ free(cells[2]);
964
+ row_count++;
965
+ } else {
966
+ for (size_t c = 0; c < ncells; c++) free(cells[c]);
967
+ }
968
+ }
969
+
970
+ for (size_t r = 0; r < row_count; r++) {
971
+ const char *tr = "<tr>\n";
972
+ size_t trl = strlen(tr);
973
+ if (trl < remaining) {
974
+ memcpy(p, tr, trl);
975
+ p += trl;
976
+ remaining -= trl;
977
+ }
978
+
979
+ if (r == 0 && col0_label && *col0_label) {
980
+ write_html_cell(&p, &remaining, &output, &total_size, "td", col0_label,
981
+ 1, row_count, true);
982
+ } else if (r > 0) {
983
+ /* rowspan covers later rows */
984
+ }
985
+
986
+ write_html_cell(&p, &remaining, &output, &total_size, "td", row_c1[r], 1, 1, true);
987
+ write_html_cell(&p, &remaining, &output, &total_size, "td", row_c2[r], 1, 1, true);
988
+
989
+ const char *endtr = "</tr>\n";
990
+ size_t etl = strlen(endtr);
991
+ if (etl < remaining) {
992
+ memcpy(p, endtr, etl);
993
+ p += etl;
994
+ remaining -= etl;
995
+ }
996
+ }
997
+
998
+ free(col0_label);
999
+ for (size_t r = 0; r < row_count; r++) {
1000
+ free(row_c1[r]);
1001
+ free(row_c2[r]);
1002
+ }
1003
+ free(row_c1);
1004
+ free(row_c2);
1005
+
1006
+ const char *close = "</tbody>\n</table>\n\n";
1007
+ size_t cl = strlen(close);
1008
+ if (cl < remaining) {
1009
+ memcpy(p, close, cl);
1010
+ p += cl;
1011
+ remaining -= cl;
1012
+ }
1013
+ *p = '\0';
1014
+ return output;
1015
+ }
1016
+
1017
+ char *apex_preprocess_grid_tables(const char *text);
1018
+
1019
+ static char *preprocess_grid_text(const char *text, bool embed_as_html);
1020
+ static char *convert_collected_grid_table(char **table_lines, size_t table_line_count,
1021
+ bool force_html);
1022
+
1023
+ static char *preprocess_cell_grid_content(const char *content) {
1024
+ if (!content || !strchr(content, '+')) return NULL;
1025
+ /* Pipe tables are not parsed inside markdown="1" cells; emit HTML for embedded grids. */
1026
+ return preprocess_grid_text(content, true);
1027
+ }
1028
+
1029
+ static char *escape_pipes_in_multiline_cell(const char *content) {
1030
+ if (!content || !strchr(content, '\n')) return NULL;
1031
+ if (!strchr(content, '|') && !strchr(content, '-')) return NULL;
1032
+
1033
+ size_t len = strlen(content);
1034
+ char *out = malloc(len * 6 + 1);
1035
+ if (!out) return NULL;
1036
+
1037
+ char *p = out;
1038
+ for (const char *s = content; *s; s++) {
1039
+ if (*s == '|') {
1040
+ memcpy(p, "&#124;", 6);
1041
+ p += 6;
1042
+ } else if (*s == '\n') {
1043
+ memcpy(p, "<br>", 4);
1044
+ p += 4;
1045
+ } else {
1046
+ *p++ = *s;
1047
+ }
1048
+ }
1049
+ *p = '\0';
1050
+ return out;
1051
+ }
1052
+
1053
+ static char *trim_grid_cell_border_pipes(char *content) {
1054
+ if (!content) return content;
1055
+ char *start = content;
1056
+ while (*start == ' ' || *start == '\t') start++;
1057
+ char *end = content + strlen(content);
1058
+ while (end > start && (end[-1] == ' ' || end[-1] == '\t' || end[-1] == '\n' || end[-1] == '\r')) end--;
1059
+ if (*start == '|') start++;
1060
+ while (end > start && (end[-1] == ' ' || end[-1] == '\t')) end--;
1061
+ if (end > start && end[-1] == '|') end--;
1062
+ while (end > start && end[-1] == ' ') end--;
1063
+ size_t len = (size_t)(end - start);
1064
+ char *out = malloc(len + 1);
1065
+ if (!out) return content;
1066
+ memcpy(out, start, len);
1067
+ out[len] = '\0';
1068
+ free(content);
1069
+ return out;
1070
+ }
1071
+
1072
+ /**
1073
+ * Trim border pipes from each grid row line before colspan cell escaping.
1074
+ */
1075
+ static char *normalize_colspan_merged_content(const char *merged) {
1076
+ if (!merged) return NULL;
1077
+
1078
+ char *result = NULL;
1079
+ const char *s = merged;
1080
+ while (*s) {
1081
+ const char *line_end = strchr(s, '\n');
1082
+ if (!line_end) line_end = s + strlen(s);
1083
+
1084
+ size_t line_len = (size_t)(line_end - s);
1085
+ char *line = malloc(line_len + 1);
1086
+ if (!line) break;
1087
+
1088
+ memcpy(line, s, line_len);
1089
+ line[line_len] = '\0';
1090
+
1091
+ if (strchr(line, '|') && !is_grid_table_separator(line) &&
1092
+ count_cells_in_line(line) <= 1) {
1093
+ line = trim_grid_cell_border_pipes(line);
1094
+ }
1095
+
1096
+ result = str_append_line(result, line);
1097
+ free(line);
1098
+
1099
+ s = *line_end ? line_end + 1 : line_end;
1100
+ }
1101
+
1102
+ return result;
1103
+ }
1104
+
1105
+ static char *convert_grid_table(char **lines, size_t line_count,
1106
+ int *alignments, size_t column_count,
1107
+ bool as_html);
589
1108
  static char *convert_grid_table_to_pipe(char **lines, size_t line_count,
590
1109
  int *alignments, size_t column_count) {
1110
+ return convert_grid_table(lines, line_count, alignments, column_count, false);
1111
+ }
1112
+ static char *convert_grid_table_to_html(char **lines, size_t line_count,
1113
+ int *alignments, size_t column_count) {
1114
+ return convert_grid_table(lines, line_count, alignments, column_count, true);
1115
+ }
1116
+
1117
+ /**
1118
+ * Convert grid table rows to pipe or HTML format
1119
+ */
1120
+ static char *convert_grid_table(char **lines, size_t line_count,
1121
+ int *alignments, size_t column_count,
1122
+ bool as_html) {
591
1123
  if (!lines || line_count == 0 || !alignments || column_count == 0) {
592
1124
  return NULL;
593
1125
  }
@@ -599,7 +1131,7 @@ static char *convert_grid_table_to_pipe(char **lines, size_t line_count,
599
1131
  total_size += strlen(lines[i]) + column_count * 4 + 10;
600
1132
  }
601
1133
  }
602
- total_size += 200; /* Extra for separator and formatting */
1134
+ total_size += as_html ? 4096 : 200;
603
1135
 
604
1136
  char *output = malloc(total_size);
605
1137
  if (!output) return NULL;
@@ -607,14 +1139,22 @@ static char *convert_grid_table_to_pipe(char **lines, size_t line_count,
607
1139
  char *p = output;
608
1140
  size_t remaining = total_size;
609
1141
 
610
- /* Add blank lines at start of table for proper recognition */
611
- *p++ = '\n';
612
- *p++ = '\n';
613
- remaining -= 2;
1142
+ if (as_html) {
1143
+ memcpy(p, "\n\n<table>\n", 10);
1144
+ p += 10;
1145
+ remaining -= 10;
1146
+ } else {
1147
+ /* Add blank lines at start of table for proper recognition */
1148
+ *p++ = '\n';
1149
+ *p++ = '\n';
1150
+ remaining -= 2;
1151
+ }
614
1152
 
615
1153
  bool separator_processed = false;
616
1154
  bool header_written = false;
617
- bool rows_written = false; /* Track if we've written any rows */
1155
+ bool rows_written = false;
1156
+ bool in_thead = false;
1157
+ bool pipe_header_promoted = false;
618
1158
 
619
1159
  /* First, find the header separator to identify header vs body */
620
1160
  size_t header_sep_idx = 0;
@@ -634,8 +1174,8 @@ static char *convert_grid_table_to_pipe(char **lines, size_t line_count,
634
1174
  const char *line = lines[i];
635
1175
  if (!line) continue;
636
1176
 
637
- /* Check if this is a separator line */
638
- if (is_grid_table_separator(line)) {
1177
+ /* Check if this is a separator line (table boundary, not nested grid in cell) */
1178
+ if (is_grid_table_separator(line) && !is_nested_grid_separator(line, column_count)) {
639
1179
  bool is_header_sep = is_header_separator(line);
640
1180
  bool is_footer_sep = false;
641
1181
 
@@ -669,27 +1209,35 @@ static char *convert_grid_table_to_pipe(char **lines, size_t line_count,
669
1209
 
670
1210
  /* Collect lines that form a single logical row */
671
1211
  const char *first_line = NULL;
1212
+ bool colspan_block = false;
672
1213
  for (size_t j = line_idx; j < i; j++) {
673
- if (lines[j] && !is_grid_table_separator(lines[j]) && strchr(lines[j], '|')) {
1214
+ if (!lines[j]) continue;
1215
+
1216
+ if (is_grid_table_separator(lines[j])) {
1217
+ if (is_nested_grid_separator(lines[j], column_count)) {
1218
+ if (first_line) row_line_count++;
1219
+ continue;
1220
+ }
1221
+ if (first_line) break;
1222
+ continue;
1223
+ }
1224
+
1225
+ if (strchr(lines[j], '|')) {
674
1226
  if (!first_line) {
675
1227
  first_line = lines[j];
676
1228
  row_line_start = j;
677
1229
  row_line_count = 1;
1230
+ colspan_block = is_full_row_colspan_line(first_line);
1231
+ } else if (colspan_block) {
1232
+ row_line_count++;
678
1233
  } else if (has_same_column_structure(first_line, lines[j])) {
679
- /* Same column structure - part of multi-line cell */
680
1234
  row_line_count++;
681
1235
  } else {
682
- /* Different column structure - new row */
683
- /* Note: Lines with nested separators (like +-------+ within cells) will be
684
- * treated as separate rows, which is correct. The nested separator will be
685
- * included as literal text in the cell content. */
686
1236
  break;
687
1237
  }
688
1238
  } else if (first_line) {
689
- /* Blank line or separator - end of current row */
690
- break;
1239
+ row_line_count++;
691
1240
  } else {
692
- /* Skip blank lines before first content line */
693
1241
  line_idx++;
694
1242
  }
695
1243
  }
@@ -703,38 +1251,230 @@ static char *convert_grid_table_to_pipe(char **lines, size_t line_count,
703
1251
  }
704
1252
 
705
1253
  size_t cell_count = 0;
706
- char **cells = extract_cells_from_row_lines(row_lines_array, row_line_count, &cell_count);
1254
+ char **cells = NULL;
1255
+ bool colspan_group = is_full_row_colspan_line(row_lines_array[0]);
1256
+
1257
+ if (colspan_group) {
1258
+ cells = malloc(MAX_COLUMNS * sizeof(char*));
1259
+ if (cells) {
1260
+ char *merged = NULL;
1261
+ for (size_t j = 0; j < row_line_count; j++) {
1262
+ merged = str_append_line(merged, row_lines_array[j]);
1263
+ }
1264
+ cells[0] = merged;
1265
+ cell_count = 1;
1266
+ }
1267
+ if (cells && cells[0] && cell_count == 1 && column_count > 1) {
1268
+ cells[0] = trim_grid_cell_border_pipes(cells[0]);
1269
+ }
1270
+ } else {
1271
+ cells = extract_cells_from_row_lines(row_lines_array, row_line_count, &cell_count);
1272
+ }
707
1273
 
708
1274
  if (cells && cell_count > 0) {
1275
+ bool force_colspan = (row_line_count > 0 && row_lines_array[0] &&
1276
+ is_full_row_colspan_line(row_lines_array[0]));
709
1277
  /* Write header separator after first header row, before first body row */
710
1278
  if (is_header_section && !header_written && !separator_processed && !is_footer_sep) {
711
1279
  header_written = true;
712
1280
  } else if (!is_header_section && !separator_processed && !is_footer_sep) {
713
- /* This is first body row - write separator before it */
714
- char *sep = create_pipe_separator(alignments, column_count);
715
- if (sep) {
716
- size_t sep_len = strlen(sep);
717
- if (sep_len < remaining) {
718
- memcpy(p, sep, sep_len);
719
- p += sep_len;
720
- remaining -= sep_len;
721
- *p++ = '\n';
722
- remaining--;
1281
+ if (!as_html && has_header_sep) {
1282
+ char *sep = create_pipe_separator(alignments, column_count);
1283
+ if (sep) {
1284
+ size_t sep_len = strlen(sep);
1285
+ if (sep_len < remaining) {
1286
+ memcpy(p, sep, sep_len);
1287
+ p += sep_len;
1288
+ remaining -= sep_len;
1289
+ *p++ = '\n';
1290
+ remaining--;
1291
+ }
1292
+ free(sep);
723
1293
  }
724
- free(sep);
1294
+ separator_processed = true;
725
1295
  }
726
- separator_processed = true;
727
1296
  }
728
1297
 
729
- /* Convert to pipe table row */
1298
+ /* Convert to pipe or HTML table row */
730
1299
  if (remaining > column_count * 50) {
1300
+ bool use_colspan = force_colspan;
1301
+ if (use_colspan && force_colspan && cells && cell_count > 0) {
1302
+ /* Collapse to single cell for colspan row */
1303
+ char *merged_one = NULL;
1304
+ for (size_t ci = 0; ci < cell_count; ci++) {
1305
+ if (cells[ci]) {
1306
+ const char *t = cells[ci];
1307
+ while (*t && isspace((unsigned char)*t)) t++;
1308
+ if (*t) {
1309
+ merged_one = strdup(cells[ci]);
1310
+ break;
1311
+ }
1312
+ }
1313
+ }
1314
+ if (!merged_one && cells[0]) merged_one = strdup(cells[0]);
1315
+ if (merged_one) {
1316
+ for (size_t ci = 0; ci < cell_count; ci++) free(cells[ci]);
1317
+ cells[0] = trim_grid_cell_border_pipes(merged_one);
1318
+ cell_count = 1;
1319
+ }
1320
+ }
1321
+
1322
+ char *colspan_processed = NULL;
1323
+ if (as_html && use_colspan && cells[0]) {
1324
+ colspan_processed = preprocess_cell_grid_content(cells[0]);
1325
+ }
1326
+
1327
+ if (as_html) {
1328
+ if (is_header_section && !separator_processed && !in_thead) {
1329
+ const char *thead = "<thead>\n";
1330
+ size_t tl = strlen(thead);
1331
+ if (tl < remaining) {
1332
+ memcpy(p, thead, tl);
1333
+ p += tl;
1334
+ remaining -= tl;
1335
+ in_thead = true;
1336
+ }
1337
+ } else if (!is_header_section && in_thead) {
1338
+ const char *end = "</thead>\n<tbody>\n";
1339
+ size_t el = strlen(end);
1340
+ if (el < remaining) {
1341
+ memcpy(p, end, el);
1342
+ p += el;
1343
+ remaining -= el;
1344
+ in_thead = false;
1345
+ }
1346
+ } else if (!is_header_section && !in_thead && !rows_written) {
1347
+ const char *tbody = "<tbody>\n";
1348
+ size_t bl = strlen(tbody);
1349
+ if (bl < remaining) {
1350
+ memcpy(p, tbody, bl);
1351
+ p += bl;
1352
+ remaining -= bl;
1353
+ }
1354
+ }
1355
+
1356
+ const char *tr = "<tr>\n";
1357
+ size_t trl = strlen(tr);
1358
+ if (trl < remaining) {
1359
+ memcpy(p, tr, trl);
1360
+ p += trl;
1361
+ remaining -= trl;
1362
+ }
1363
+
1364
+ size_t cells_to_write = use_colspan ? 1 : cell_count;
1365
+ for (size_t k = 0; k < cells_to_write; k++) {
1366
+ const char *cell_content = (k < cell_count && cells[k]) ? cells[k] : "";
1367
+ if (use_colspan) {
1368
+ cell_content = colspan_processed ? colspan_processed : cells[0];
1369
+ }
1370
+
1371
+ const char *tag = (is_header_section && !separator_processed) ? "th" : "td";
1372
+ size_t span = 1;
1373
+ if (use_colspan && column_count > 1) {
1374
+ span = column_count;
1375
+ } else if (!use_colspan && cell_count < column_count && k == 0) {
1376
+ span = column_count - cell_count + 1;
1377
+ }
1378
+
1379
+ char open[256];
1380
+ if (span > 1) {
1381
+ snprintf(open, sizeof(open),
1382
+ "<%s markdown=\"1\" colspan=\"%zu\">\n\n",
1383
+ tag, span);
1384
+ } else {
1385
+ snprintf(open, sizeof(open),
1386
+ "<%s markdown=\"1\">\n\n", tag);
1387
+ }
1388
+ size_t ol = strlen(open);
1389
+ if (ol < remaining) {
1390
+ memcpy(p, open, ol);
1391
+ p += ol;
1392
+ remaining -= ol;
1393
+ }
1394
+ size_t cl = strlen(cell_content);
1395
+ if (cl < remaining) {
1396
+ memcpy(p, cell_content, cl);
1397
+ p += cl;
1398
+ remaining -= cl;
1399
+ }
1400
+ char close[16];
1401
+ snprintf(close, sizeof(close), "\n\n</%s>\n", tag);
1402
+ size_t cll = strlen(close);
1403
+ if (cll < remaining) {
1404
+ memcpy(p, close, cll);
1405
+ p += cll;
1406
+ remaining -= cll;
1407
+ }
1408
+ }
1409
+
1410
+ const char *endtr = "</tr>\n";
1411
+ size_t etl = strlen(endtr);
1412
+ if (etl < remaining) {
1413
+ memcpy(p, endtr, etl);
1414
+ p += etl;
1415
+ remaining -= etl;
1416
+ }
1417
+
1418
+ if (colspan_processed) free(colspan_processed);
1419
+ rows_written = true;
1420
+ } else {
1421
+ if (use_colspan && cells[0]) {
1422
+ char *tmp_trim = strdup(cells[0]);
1423
+ if (tmp_trim) tmp_trim = trim_grid_cell_border_pipes(tmp_trim);
1424
+ const char *content = tmp_trim ? tmp_trim : cells[0];
1425
+
1426
+ *p++ = '|';
1427
+ remaining--;
1428
+ *p++ = ' ';
1429
+ remaining--;
1430
+ size_t cl = strlen(content);
1431
+ if (cl < remaining - 10) {
1432
+ memcpy(p, content, cl);
1433
+ p += cl;
1434
+ remaining -= cl;
1435
+ }
1436
+ *p++ = ' ';
1437
+ remaining--;
1438
+ *p++ = '|';
1439
+ remaining--;
1440
+ const char *marker = " << |";
1441
+ size_t ml = strlen(marker);
1442
+ if (ml < remaining) {
1443
+ memcpy(p, marker, ml);
1444
+ p += ml;
1445
+ remaining -= ml;
1446
+ }
1447
+ *p++ = '\n';
1448
+ remaining--;
1449
+ if (tmp_trim) free(tmp_trim);
1450
+ rows_written = true;
1451
+ } else {
731
1452
  *p++ = '|';
732
1453
  remaining--;
733
1454
 
734
1455
  for (size_t k = 0; k < column_count; k++) {
735
- const char *cell_content = (k < cell_count && cells[k]) ? cells[k] : "";
1456
+ const char *cell_content = "";
1457
+ char *escaped = NULL;
1458
+ char *tmp_trim = NULL;
1459
+ if (use_colspan) {
1460
+ cell_content = (k == 0) ? cells[0] : "<<";
1461
+ } else if (k < cell_count && cells[k]) {
1462
+ cell_content = cells[k];
1463
+ }
1464
+
1465
+ if (use_colspan && k == 0 && cell_content && *cell_content) {
1466
+ tmp_trim = strdup(cell_content);
1467
+ if (tmp_trim) {
1468
+ tmp_trim = trim_grid_cell_border_pipes(tmp_trim);
1469
+ cell_content = tmp_trim;
1470
+ }
1471
+ }
1472
+
1473
+ if (cell_content && !use_colspan && strchr(cell_content, '\n')) {
1474
+ escaped = escape_pipes_in_multiline_cell(cell_content);
1475
+ if (escaped) cell_content = escaped;
1476
+ }
736
1477
 
737
- /* Trim leading/trailing whitespace from cell content */
738
1478
  const char *content_start = cell_content;
739
1479
  const char *content_end = cell_content + strlen(cell_content);
740
1480
  while (content_start < content_end && isspace((unsigned char)*content_start)) {
@@ -745,26 +1485,44 @@ static char *convert_grid_table_to_pipe(char **lines, size_t line_count,
745
1485
  }
746
1486
  size_t content_len = content_end - content_start;
747
1487
 
748
- if (content_len > 0 && content_len < remaining - 5) {
749
- *p++ = ' ';
750
- remaining--;
1488
+ *p++ = ' ';
1489
+ remaining--;
1490
+ if (content_len > 0 && content_len < remaining - 2) {
751
1491
  memcpy(p, content_start, content_len);
752
1492
  p += content_len;
753
1493
  remaining -= content_len;
754
- *p++ = ' ';
755
- remaining--;
756
- } else {
757
- *p++ = ' ';
758
- remaining--;
759
1494
  }
760
-
1495
+ *p++ = ' ';
1496
+ remaining--;
761
1497
  *p++ = '|';
762
1498
  remaining--;
1499
+ if (escaped) free(escaped);
1500
+ if (tmp_trim) free(tmp_trim);
763
1501
  }
764
1502
 
765
1503
  *p++ = '\n';
766
1504
  remaining--;
767
- rows_written = true; /* Mark that we've written a row */
1505
+ rows_written = true;
1506
+
1507
+ if (!has_header_sep && !pipe_header_promoted &&
1508
+ !is_header_section && !use_colspan) {
1509
+ char *sep = create_pipe_separator(alignments, column_count);
1510
+ if (sep) {
1511
+ size_t sep_len = strlen(sep);
1512
+ if (sep_len < remaining) {
1513
+ memcpy(p, sep, sep_len);
1514
+ p += sep_len;
1515
+ remaining -= sep_len;
1516
+ *p++ = '\n';
1517
+ remaining--;
1518
+ }
1519
+ free(sep);
1520
+ }
1521
+ pipe_header_promoted = true;
1522
+ separator_processed = true;
1523
+ }
1524
+ }
1525
+ }
768
1526
  }
769
1527
 
770
1528
  /* Free cells */
@@ -786,23 +1544,7 @@ static char *convert_grid_table_to_pipe(char **lines, size_t line_count,
786
1544
 
787
1545
  /* Write separator after header rows if this is the header separator */
788
1546
  if (is_header_sep && !is_footer_sep && !separator_processed && header_written) {
789
- char *sep = create_pipe_separator(alignments, column_count);
790
- if (sep) {
791
- size_t sep_len = strlen(sep);
792
- if (sep_len < remaining) {
793
- memcpy(p, sep, sep_len);
794
- p += sep_len;
795
- remaining -= sep_len;
796
- *p++ = '\n';
797
- remaining--;
798
- }
799
- free(sep);
800
- }
801
- separator_processed = true;
802
- } else if (!is_header_sep && !is_footer_sep && !separator_processed && rows_written) {
803
- /* Regular separator (not header) - write separator after rows have been written */
804
- /* But only if we haven't written header separator yet */
805
- if (!has_header_sep || i > header_sep_idx) {
1547
+ if (!as_html) {
806
1548
  char *sep = create_pipe_separator(alignments, column_count);
807
1549
  if (sep) {
808
1550
  size_t sep_len = strlen(sep);
@@ -815,6 +1557,25 @@ static char *convert_grid_table_to_pipe(char **lines, size_t line_count,
815
1557
  }
816
1558
  free(sep);
817
1559
  }
1560
+ }
1561
+ separator_processed = true;
1562
+ } else if (!is_header_sep && !is_footer_sep && !separator_processed && rows_written &&
1563
+ has_header_sep) {
1564
+ if (i > header_sep_idx) {
1565
+ if (!as_html) {
1566
+ char *sep = create_pipe_separator(alignments, column_count);
1567
+ if (sep) {
1568
+ size_t sep_len = strlen(sep);
1569
+ if (sep_len < remaining) {
1570
+ memcpy(p, sep, sep_len);
1571
+ p += sep_len;
1572
+ remaining -= sep_len;
1573
+ *p++ = '\n';
1574
+ remaining--;
1575
+ }
1576
+ free(sep);
1577
+ }
1578
+ }
818
1579
  separator_processed = true;
819
1580
  }
820
1581
  }
@@ -826,6 +1587,98 @@ static char *convert_grid_table_to_pipe(char **lines, size_t line_count,
826
1587
 
827
1588
  /* Process final rows after last separator */
828
1589
  if (row_start < line_count) {
1590
+ size_t first_content = row_start;
1591
+ for (; first_content < line_count; first_content++) {
1592
+ if (lines[first_content] && strchr(lines[first_content], '|')) break;
1593
+ }
1594
+
1595
+ bool final_colspan = (first_content < line_count &&
1596
+ is_full_row_colspan_line(lines[first_content]));
1597
+
1598
+ if (final_colspan) {
1599
+ char *merged = NULL;
1600
+ for (size_t j = row_start; j < line_count; j++) {
1601
+ if (lines[j]) merged = str_append_line(merged, lines[j]);
1602
+ }
1603
+
1604
+ if (merged && remaining > column_count * 50) {
1605
+ char *normalized = normalize_colspan_merged_content(merged);
1606
+ if (normalized) {
1607
+ free(merged);
1608
+ merged = normalized;
1609
+ }
1610
+
1611
+ char *escaped = NULL;
1612
+ const char *content = merged;
1613
+ char *processed = NULL;
1614
+ if (as_html) {
1615
+ processed = preprocess_cell_grid_content(merged);
1616
+ if (processed) content = processed;
1617
+ } else {
1618
+ escaped = escape_pipes_in_multiline_cell(merged);
1619
+ if (escaped) content = escaped;
1620
+ }
1621
+
1622
+ if (as_html) {
1623
+ if (in_thead) {
1624
+ const char *end = "</thead>\n<tbody>\n";
1625
+ size_t el = strlen(end);
1626
+ if (el < remaining) {
1627
+ memcpy(p, end, el);
1628
+ p += el;
1629
+ remaining -= el;
1630
+ in_thead = false;
1631
+ }
1632
+ } else if (!rows_written) {
1633
+ const char *tbody = "<tbody>\n";
1634
+ size_t bl = strlen(tbody);
1635
+ if (bl < remaining) {
1636
+ memcpy(p, tbody, bl);
1637
+ p += bl;
1638
+ remaining -= bl;
1639
+ }
1640
+ }
1641
+
1642
+ char open[64];
1643
+ snprintf(open, sizeof(open), "<tr>\n<td markdown=\"1\" colspan=\"%zu\">\n\n", column_count);
1644
+ size_t ol = strlen(open);
1645
+ if (ol < remaining) { memcpy(p, open, ol); p += ol; remaining -= ol; }
1646
+ size_t cl = strlen(content);
1647
+ if (cl < remaining) { memcpy(p, content, cl); p += cl; remaining -= cl; }
1648
+ const char *close = "\n\n</td>\n</tr>\n";
1649
+ size_t cll = strlen(close);
1650
+ if (cll < remaining) { memcpy(p, close, cll); p += cll; remaining -= cll; }
1651
+ rows_written = true;
1652
+ } else {
1653
+ *p++ = '|';
1654
+ remaining--;
1655
+ *p++ = ' ';
1656
+ remaining--;
1657
+ size_t cl = strlen(content);
1658
+ if (cl < remaining - 10) {
1659
+ memcpy(p, content, cl);
1660
+ p += cl;
1661
+ remaining -= cl;
1662
+ }
1663
+ *p++ = ' ';
1664
+ remaining--;
1665
+ *p++ = '|';
1666
+ remaining--;
1667
+ const char *marker = " << |";
1668
+ size_t ml = strlen(marker);
1669
+ if (ml < remaining) {
1670
+ memcpy(p, marker, ml);
1671
+ p += ml;
1672
+ remaining -= ml;
1673
+ }
1674
+ *p++ = '\n';
1675
+ remaining--;
1676
+ }
1677
+ if (escaped) free(escaped);
1678
+ if (processed) free(processed);
1679
+ free(merged);
1680
+ }
1681
+ } else {
829
1682
  char **row_lines = malloc((line_count - row_start) * sizeof(char*));
830
1683
  if (row_lines) {
831
1684
  size_t row_line_count = 0;
@@ -888,24 +1741,109 @@ static char *convert_grid_table_to_pipe(char **lines, size_t line_count,
888
1741
  }
889
1742
  free(row_lines);
890
1743
  }
1744
+ }
891
1745
  }
892
1746
 
893
- /* Add blank line at end of table */
894
- if (p > output && p[-1] != '\n') {
1747
+ if (as_html) {
1748
+ if (in_thead) {
1749
+ const char *end = "</thead>\n<tbody>\n";
1750
+ size_t el = strlen(end);
1751
+ if (el < remaining) {
1752
+ memcpy(p, end, el);
1753
+ p += el;
1754
+ remaining -= el;
1755
+ }
1756
+ }
1757
+ const char *close = "</tbody>\n</table>\n\n";
1758
+ size_t cl = strlen(close);
1759
+ if (cl < remaining) {
1760
+ memcpy(p, close, cl);
1761
+ p += cl;
1762
+ remaining -= cl;
1763
+ }
1764
+ } else {
1765
+ /* Add blank line at end of table */
1766
+ if (p > output && p[-1] != '\n') {
1767
+ *p++ = '\n';
1768
+ remaining--;
1769
+ }
895
1770
  *p++ = '\n';
896
1771
  remaining--;
897
1772
  }
898
- *p++ = '\n';
899
- remaining--;
900
1773
 
901
1774
  *p = '\0';
902
1775
  return output;
903
1776
  }
904
1777
 
905
- /**
906
- * Main preprocessing function
907
- */
908
- char *apex_preprocess_grid_tables(const char *text) {
1778
+ static char *convert_collected_grid_table(char **table_lines, size_t table_line_count,
1779
+ bool force_html) {
1780
+ if (!table_lines || table_line_count == 0) return NULL;
1781
+
1782
+ int alignments[MAX_COLUMNS] = {0};
1783
+ size_t column_count = 0;
1784
+ size_t first_sep_cols = 0;
1785
+
1786
+ for (size_t i = 0; i < table_line_count; i++) {
1787
+ if (is_grid_table_separator(table_lines[i]) && first_sep_cols == 0) {
1788
+ parse_alignment(table_lines[i], alignments, &first_sep_cols);
1789
+ column_count = first_sep_cols;
1790
+ }
1791
+ }
1792
+
1793
+ for (size_t i = 0; i < table_line_count; i++) {
1794
+ if (is_header_separator(table_lines[i])) {
1795
+ size_t sep_cols = 0;
1796
+ int sep_alignments[MAX_COLUMNS] = {0};
1797
+ parse_alignment(table_lines[i], sep_alignments, &sep_cols);
1798
+ if (sep_cols > column_count) {
1799
+ column_count = sep_cols;
1800
+ memcpy(alignments, sep_alignments, sizeof(sep_alignments));
1801
+ }
1802
+ break;
1803
+ }
1804
+ }
1805
+
1806
+ if (column_count == 0) {
1807
+ for (size_t i = 0; i < table_line_count; i++) {
1808
+ if (is_grid_table_separator(table_lines[i])) {
1809
+ parse_alignment(table_lines[i], alignments, &column_count);
1810
+ if (column_count > 0) break;
1811
+ }
1812
+ }
1813
+ }
1814
+
1815
+ if (column_count == 0) {
1816
+ for (size_t i = 0; i < table_line_count; i++) {
1817
+ if (strchr(table_lines[i], '|') && !is_grid_table_separator(table_lines[i])) {
1818
+ size_t row_cols = count_cells_in_line(table_lines[i]);
1819
+ if (row_cols > column_count) {
1820
+ column_count = row_cols;
1821
+ }
1822
+ }
1823
+ }
1824
+ for (size_t j = 0; j < column_count && j < MAX_COLUMNS; j++) {
1825
+ alignments[j] = 0;
1826
+ }
1827
+ }
1828
+
1829
+ if (column_count == 0) return NULL;
1830
+
1831
+ if (table_has_partial_separators(table_lines, table_line_count)) {
1832
+ return convert_partial_grid_table_to_html(table_lines, table_line_count, column_count);
1833
+ }
1834
+
1835
+ if (force_html ||
1836
+ table_has_nested_grid(table_lines, table_line_count, column_count) ||
1837
+ table_has_multiline_cells(table_lines, table_line_count)) {
1838
+ return convert_grid_table_to_html(table_lines, table_line_count,
1839
+ alignments, column_count);
1840
+ }
1841
+
1842
+ return convert_grid_table_to_pipe(table_lines, table_line_count,
1843
+ alignments, column_count);
1844
+ }
1845
+
1846
+ static char *preprocess_grid_text(const char *text, bool embed_as_html) {
909
1847
  if (!text) return NULL;
910
1848
 
911
1849
  size_t len = strlen(text);
@@ -1005,48 +1943,16 @@ char *apex_preprocess_grid_tables(const char *text) {
1005
1943
  }
1006
1944
 
1007
1945
  if (table_line_count > 0) {
1008
- /* Parse alignment from separator lines and find maximum column count */
1009
- int alignments[MAX_COLUMNS] = {0};
1010
- size_t column_count = 0;
1011
-
1012
- /* First pass: find maximum column count from all separators */
1013
- for (size_t i = 0; i < table_line_count; i++) {
1014
- if (is_grid_table_separator(table_lines[i])) {
1015
- size_t sep_cols = 0;
1016
- int sep_alignments[MAX_COLUMNS] = {0};
1017
- parse_alignment(table_lines[i], sep_alignments, &sep_cols);
1018
- if (sep_cols > column_count) {
1019
- column_count = sep_cols;
1020
- /* Use alignments from the separator with most columns */
1021
- memcpy(alignments, sep_alignments, sizeof(sep_alignments));
1022
- }
1023
- }
1024
- }
1025
-
1026
- /* If no separator found, try to infer from content rows */
1027
- if (column_count == 0) {
1028
- for (size_t i = 0; i < table_line_count; i++) {
1029
- if (strchr(table_lines[i], '|') && !is_grid_table_separator(table_lines[i])) {
1030
- size_t row_cols = count_cells_in_line(table_lines[i]);
1031
- if (row_cols > column_count) {
1032
- column_count = row_cols;
1033
- }
1034
- }
1035
- }
1036
- /* Set default alignments */
1037
- for (size_t j = 0; j < column_count && j < MAX_COLUMNS; j++) {
1038
- alignments[j] = 0; /* Default left */
1039
- }
1946
+ char *table_output = NULL;
1947
+ if (grid_block_has_separator(table_lines, table_line_count) &&
1948
+ grid_block_has_content_rows(table_lines, table_line_count)) {
1949
+ table_output = convert_collected_grid_table(table_lines, table_line_count,
1950
+ embed_as_html);
1040
1951
  }
1041
1952
 
1042
- /* Convert grid table to pipe table */
1043
- char *pipe_table = convert_grid_table_to_pipe(table_lines, table_line_count,
1044
- alignments, column_count);
1953
+ if (table_output) {
1954
+ size_t pipe_len = strlen(table_output);
1045
1955
 
1046
- if (pipe_table) {
1047
- size_t pipe_len = strlen(pipe_table);
1048
-
1049
- /* Ensure we have enough space */
1050
1956
  if (remaining < pipe_len) {
1051
1957
  size_t written = write - output;
1052
1958
  cap = (written + pipe_len + 100) * 2;
@@ -1058,17 +1964,40 @@ char *apex_preprocess_grid_tables(const char *text) {
1058
1964
  }
1059
1965
  }
1060
1966
 
1061
- /* Write the table (it already has blank lines at start and end) */
1062
1967
  if (pipe_len <= remaining) {
1063
- memcpy(write, pipe_table, pipe_len);
1968
+ memcpy(write, table_output, pipe_len);
1064
1969
  write += pipe_len;
1065
1970
  remaining -= pipe_len;
1066
1971
  }
1067
1972
 
1068
- free(pipe_table);
1973
+ free(table_output);
1974
+ } else {
1975
+ /* Not a valid grid or conversion failed: preserve source lines */
1976
+ for (size_t i = 0; i < table_line_count; i++) {
1977
+ if (!table_lines[i]) continue;
1978
+ size_t tl = strlen(table_lines[i]);
1979
+ if (remaining < tl + 2) {
1980
+ size_t written = write - output;
1981
+ cap = (written + tl + 100) * 2;
1982
+ char *new_output = realloc(output, cap);
1983
+ if (new_output) {
1984
+ output = new_output;
1985
+ write = output + written;
1986
+ remaining = cap - written;
1987
+ }
1988
+ }
1989
+ if (tl <= remaining) {
1990
+ memcpy(write, table_lines[i], tl);
1991
+ write += tl;
1992
+ remaining -= tl;
1993
+ }
1994
+ if (remaining > 0) {
1995
+ *write++ = '\n';
1996
+ remaining--;
1997
+ }
1998
+ }
1069
1999
  }
1070
2000
 
1071
- /* Free table lines */
1072
2001
  for (size_t i = 0; i < table_line_count; i++) {
1073
2002
  if (table_lines[i]) free(table_lines[i]);
1074
2003
  }
@@ -1076,9 +2005,6 @@ char *apex_preprocess_grid_tables(const char *text) {
1076
2005
 
1077
2006
  free(table_lines);
1078
2007
 
1079
- /* read is already pointing past all the table lines we just processed
1080
- * (it was updated in the loop at line 1009) */
1081
- /* Skip the blank line if present */
1082
2008
  if (*read == '\n') {
1083
2009
  read++;
1084
2010
  }
@@ -1095,7 +2021,6 @@ char *apex_preprocess_grid_tables(const char *text) {
1095
2021
  remaining--;
1096
2022
  }
1097
2023
  } else {
1098
- /* Need to reallocate */
1099
2024
  size_t written = write - output;
1100
2025
  cap = (written + line_len + 1) * 2;
1101
2026
  char *new_output = realloc(output, cap);
@@ -1119,3 +2044,7 @@ char *apex_preprocess_grid_tables(const char *text) {
1119
2044
  *write = '\0';
1120
2045
  return output;
1121
2046
  }
2047
+
2048
+ char *apex_preprocess_grid_tables(const char *text) {
2049
+ return preprocess_grid_text(text, false);
2050
+ }