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
@@ -339,8 +339,11 @@ static char *get_caption_from_paragraph(cmark_node *para) {
339
339
  if (!link_text || cmark_node_get_type(link_text) != CMARK_NODE_TEXT) return NULL;
340
340
  const char *text = cmark_node_get_literal(link_text);
341
341
  if (!text) return NULL;
342
- /* Only treat as caption if it's the only content (no siblings) */
343
- if (cmark_node_next(link_text) != NULL) return NULL;
342
+ /* Only treat as caption if paragraph contains exactly one link node.
343
+ * Footnote definitions like "[^1]: ..." can parse as LINK + TEXT;
344
+ * those must never be treated as table captions. */
345
+ if (cmark_node_next(link_text) != NULL) return NULL; /* extra content inside link */
346
+ if (cmark_node_next(child) != NULL) return NULL; /* extra paragraph content after link */
344
347
  return strdup(text);
345
348
  }
346
349
  return NULL;
@@ -1089,49 +1092,14 @@ char *apex_inject_table_attributes(const char *html, cmark_node *document, int c
1089
1092
  col_idx = 0;
1090
1093
  prev_cell_matching = NULL; /* Reset previous cell matching for new row */
1091
1094
 
1092
- /* Map HTML row index to AST row index.
1093
- * HTML rows skip separator rows (which are marked for removal in AST).
1094
- * So we need to find the AST row that corresponds to this HTML row.
1095
+ /* Map HTML row index to AST row index 1:1.
1095
1096
  *
1096
- * IMPORTANT: row_idx includes ALL <tr> tags, including the header in <thead>.
1097
- * But the header row is handled separately, so for tbody/tfoot rows, we need to
1098
- * account for that. The header is typically AST row 0, and it's the first HTML row.
1099
- * So for tbody/tfoot rows, row_idx will be >= 1 (1 = first data row, 2 = second data row, etc.).
1100
- *
1101
- * The mapping should count all non-removed rows, including the header, to match row_idx.
1102
- * Note: This mapping is only needed when we have attributes to process. */
1103
- ast_row_idx = -1;
1104
- if (all_cells) {
1105
- int html_row_count = -1; /* Start at -1, will be 0 for header */
1106
- for (int r = 0; r < 100; r++) { /* Check up to 100 AST rows */
1107
- /* Check if this AST row has any non-removed cells */
1108
- bool has_non_removed = false;
1109
- for (all_cell *c = all_cells; c; c = c->next) {
1110
- if (c->table_index == table_idx &&
1111
- c->row_index == r &&
1112
- !c->is_removed) {
1113
- has_non_removed = true;
1114
- break;
1115
- }
1116
- }
1117
- /* If this AST row has non-removed cells, it appears in HTML */
1118
- if (has_non_removed) {
1119
- html_row_count++;
1120
- if (html_row_count == row_idx) {
1121
- ast_row_idx = r;
1122
- break;
1123
- }
1124
- }
1125
- }
1126
- } else {
1127
- /* No all_cells available (alignment-only processing) - use row_idx directly */
1128
- ast_row_idx = row_idx;
1129
- }
1130
-
1131
- if (ast_row_idx == -1) {
1132
- /* Fallback: use row_idx directly */
1133
- ast_row_idx = row_idx;
1134
- }
1097
+ * cmark still emits rows that the AST marks data-remove (dummy headerless
1098
+ * thead, em-dash separator leftovers, === markers). Those <tr> tags are
1099
+ * present in the HTML we walk here and are stripped only when we decide
1100
+ * should_skip_row. Counting only non-removed AST rows desyncs indices and
1101
+ * can apply a later row's colspan onto earlier blank cells (| |). */
1102
+ ast_row_idx = row_idx;
1135
1103
 
1136
1104
  /* Pre-calculate which original columns will be visible in this row's HTML.
1137
1105
  * This creates a mapping: HTML position -> original column index.
@@ -1406,13 +1374,55 @@ char *apex_inject_table_attributes(const char *html, cmark_node *document, int c
1406
1374
  /* Note: Once we're in tfoot, we stay in tfoot - don't reopen tbody */
1407
1375
  /* tfoot rows should be at the end of the table */
1408
1376
 
1409
- /* Check if this row should be completely removed (all cells marked for removal) */
1410
- /* For tfoot rows that are pure === markers (all cells marked), skip the entire row */
1411
- /* For tfoot rows with actual content, render them normally */
1377
+ /* Check if this row should be completely removed (all cells marked for removal).
1378
+ * Always skip fully-removed rows (dummy headerless thead, em-dash separators, ===),
1379
+ * even among the first HTML rows force_keep only protects real data rows. */
1412
1380
  bool should_skip_row = false;
1381
+ bool row_fully_removed = false;
1382
+
1383
+ if (all_cells) {
1384
+ int total_cells_in_row = 0;
1385
+ int removed_cells_in_row = 0;
1386
+ for (all_cell *c = all_cells; c; c = c->next) {
1387
+ if (c->table_index == table_idx && c->row_index == ast_row_idx) {
1388
+ total_cells_in_row++;
1389
+ if (c->is_removed) {
1390
+ removed_cells_in_row++;
1391
+ }
1392
+ }
1393
+ }
1394
+ if (total_cells_in_row > 0 && total_cells_in_row == removed_cells_in_row) {
1395
+ /* Fully-removed === marker rows: drop the whole <tr>.
1396
+ * Em-dash separators and empty dummy headers: keep <tr>, remove
1397
+ * cells individually so an empty <tr></tr> remains (needed for
1398
+ * relaxed-header detection and empty-thead cleanup). */
1399
+ bool is_equals_row = false;
1400
+ if (attrs) {
1401
+ for (cell_attr *a = attrs; a; a = a->next) {
1402
+ if (a->table_index != table_idx || a->row_index != ast_row_idx)
1403
+ continue;
1404
+ if (!a->cell_text) continue;
1405
+ const char *text = a->cell_text;
1406
+ while (*text && isspace((unsigned char)*text)) text++;
1407
+ if (text[0] == '=' && text[1] == '=' && text[2] == '=') {
1408
+ is_equals_row = true;
1409
+ break;
1410
+ }
1411
+ }
1412
+ }
1413
+ if (is_equals_row) {
1414
+ row_fully_removed = true;
1415
+ should_skip_row = true;
1416
+ } else {
1417
+ row_fully_removed = false;
1418
+ should_skip_row = false;
1419
+ }
1420
+ }
1421
+ }
1413
1422
 
1414
1423
  /* CRITICAL SAFEGUARD: If this is one of the first few rows (row_idx <= 3), it's
1415
- * almost certainly in tbody, not tfoot. NEVER skip it.
1424
+ * almost certainly in tbody, not tfoot. Do not skip via tfoot/=== heuristics.
1425
+ * Fully-removed rows stay skipped (row_fully_removed).
1416
1426
  * Note: row_idx is incremented before this check (line 894), so:
1417
1427
  * - row_idx 1 = header
1418
1428
  * - row_idx 2 = first data row
@@ -1423,7 +1433,7 @@ char *apex_inject_table_attributes(const char *html, cmark_node *document, int c
1423
1433
  * This check must run FIRST, before any other skip logic, to ensure these rows
1424
1434
  * are always rendered. */
1425
1435
  bool force_keep_row = false;
1426
- if (row_idx <= 3) {
1436
+ if (!row_fully_removed && row_idx <= 3) {
1427
1437
  /* First few rows (header + first two data rows) are always in tbody - never skip them */
1428
1438
  force_keep_row = true;
1429
1439
  should_skip_row = false;
@@ -1434,11 +1444,11 @@ char *apex_inject_table_attributes(const char *html, cmark_node *document, int c
1434
1444
  /* CRITICAL: Only check if row should be skipped if we haven't already determined
1435
1445
  * it should be in tbody. If current_row_is_tfoot is false, this row was either
1436
1446
  * never marked as tfoot OR was forced to tbody because it appears before === in HTML.
1437
- * In either case, it should NOT be skipped, even if all cells are marked for removal.
1447
+ * Fully-removed rows are already handled via row_fully_removed.
1438
1448
  *
1439
1449
  * IMPORTANT: We must check this AFTER setting current_row_is_tfoot, so we know
1440
1450
  * if the row was forced to tbody. */
1441
- if (!force_keep_row && current_row_is_tfoot) {
1451
+ if (!row_fully_removed && !force_keep_row && current_row_is_tfoot) {
1442
1452
  /* Count ALL cells in this row (using all_cells) to see if any are non-removed */
1443
1453
  int total_cells_in_row = 0;
1444
1454
  int removed_cells_in_row = 0;
@@ -1574,15 +1584,16 @@ char *apex_inject_table_attributes(const char *html, cmark_node *document, int c
1574
1584
  *
1575
1585
  * FINAL OVERRIDE: This is the last check before skipping, so it must override any
1576
1586
  * previous skip decisions. Also check force_keep_row flag. */
1577
- if (force_keep_row || was_forced_to_tbody || (final_min_equals_row_idx >= 0 && row_idx <= 3) ||
1578
- (min_equals_row_idx >= 0 && row_idx <= 3)) {
1587
+ if (!row_fully_removed && (force_keep_row || was_forced_to_tbody || (final_min_equals_row_idx >= 0 && row_idx <= 3) ||
1588
+ (min_equals_row_idx >= 0 && row_idx <= 3))) {
1579
1589
  should_skip_row = false;
1580
1590
  current_row_is_tfoot = false;
1581
1591
  }
1582
1592
 
1583
1593
  /* FINAL CHECK: If force_keep_row is true, NEVER skip this row, regardless of should_skip_row.
1584
- * Also, if row_idx <= 3, protect it as an extra safeguard. */
1585
- if (force_keep_row || row_idx <= 3) {
1594
+ * Also, if row_idx <= 3, protect it as an extra safeguard.
1595
+ * Fully-removed rows (dummy/separator/===) stay skipped. */
1596
+ if (!row_fully_removed && (force_keep_row || row_idx <= 3)) {
1586
1597
  should_skip_row = false;
1587
1598
  /* If this is one of the first few rows and there's a === row, it's definitely in tbody */
1588
1599
  if (row_idx <= 3 && min_equals_row_idx >= 0) {
@@ -1875,6 +1886,10 @@ char *apex_inject_table_attributes(const char *html, cmark_node *document, int c
1875
1886
  /* Compare (case-sensitive, but we can make it more lenient if needed) */
1876
1887
  content_matches = (strncmp(attr_text, html_text, strlen(attr_text)) == 0 &&
1877
1888
  (html_text[strlen(attr_text)] == '\0' || isspace((unsigned char)html_text[strlen(attr_text)])));
1889
+ } else {
1890
+ /* Non-empty AST text must not match an empty HTML cell.
1891
+ * Otherwise colspan from |full||||| attaches to | | blanks. */
1892
+ content_matches = false;
1878
1893
  }
1879
1894
  }
1880
1895
  if (content_matches) {
@@ -1884,47 +1899,25 @@ char *apex_inject_table_attributes(const char *html, cmark_node *document, int c
1884
1899
  }
1885
1900
  }
1886
1901
 
1887
- /* If no match found in current row, also check the previous AST row.
1888
- * This is especially important for tfoot rows with === markers, where the row mapping
1889
- * might skip the === row if all its cells are marked for removal. */
1902
+ /* If no match found in current row, also check the previous AST row
1903
+ * but ONLY for === tfoot marker cells. Em-dash separator rows are also
1904
+ * data-remove; applying those to the next row would delete legitimate
1905
+ * blank cells (| |). */
1890
1906
  if (!matching && ast_row_idx > 0) {
1891
1907
  for (cell_attr *a = attrs; a; a = a->next) {
1892
1908
  if (a->table_index == table_idx &&
1893
1909
  a->row_index == ast_row_idx - 1 &&
1894
1910
  a->col_index == target_original_col &&
1895
- strstr(a->attributes, "data-remove")) {
1896
- /* Check if content matches (for === cells) - extract if needed */
1897
- bool content_matches = true;
1898
- if (a->cell_text && a->cell_text[0] != '\0') {
1899
- if (cell_preview[0] == '\0') {
1900
- bool is_th = strncmp(read, "<th", 3) == 0;
1901
- const char *close_tag = is_th ? "</th>" : "</td>";
1902
- const char *content_start = strchr(read, '>');
1903
- if (content_start) {
1904
- const char *content_end = strstr(content_start + 1, close_tag);
1905
- if (content_end && content_end - content_start - 1 < 99) {
1906
- size_t len = content_end - content_start - 1;
1907
- strncpy(cell_preview, content_start + 1, len);
1908
- cell_preview[len] = '\0';
1909
- while (len > 0 && (cell_preview[len-1] == '\n' || cell_preview[len-1] == '\r' || isspace((unsigned char)cell_preview[len-1]))) {
1910
- cell_preview[--len] = '\0';
1911
- }
1912
- }
1913
- }
1914
- }
1915
- if (cell_preview[0] != '\0') {
1916
- const char *attr_text = a->cell_text;
1917
- const char *html_text = cell_preview;
1918
- while (*attr_text && isspace((unsigned char)*attr_text)) attr_text++;
1919
- while (*html_text && isspace((unsigned char)*html_text)) html_text++;
1920
- content_matches = (strncmp(attr_text, html_text, strlen(attr_text)) == 0 &&
1921
- (html_text[strlen(attr_text)] == '\0' || isspace((unsigned char)html_text[strlen(attr_text)])));
1922
- }
1923
- }
1924
- if (content_matches) {
1925
- matching = a;
1926
- break;
1911
+ strstr(a->attributes, "data-remove") &&
1912
+ a->cell_text) {
1913
+ const char *marker = a->cell_text;
1914
+ while (*marker && isspace((unsigned char)*marker)) marker++;
1915
+ if (!(marker[0] == '=' && marker[1] == '=' && marker[2] == '=')) {
1916
+ continue;
1927
1917
  }
1918
+ /* === marker from previous row — treat as match for removal */
1919
+ matching = a;
1920
+ break;
1928
1921
  }
1929
1922
  }
1930
1923
  }
@@ -2065,8 +2058,6 @@ char *apex_inject_table_attributes(const char *html, cmark_node *document, int c
2065
2058
  if (attr_len > 0 && html_len > 0 &&
2066
2059
  attr_len == html_len &&
2067
2060
  strncmp(attr_text, html_text, attr_len) == 0) {
2068
- fprintf(stderr, "DEBUG MATCH: Found colspan cell by content fallback (same row) - text=[%s] attrs=[%s]\n",
2069
- html_text, a->attributes);
2070
2061
  matching = a;
2071
2062
  break;
2072
2063
  }