kreuzberg 4.0.0.pre.rc.29 → 4.0.0.rc1

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 (321) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +0 -6
  3. data/.rubocop.yaml +534 -1
  4. data/Gemfile +2 -1
  5. data/Gemfile.lock +28 -116
  6. data/README.md +269 -629
  7. data/Rakefile +0 -9
  8. data/Steepfile +4 -8
  9. data/examples/async_patterns.rb +58 -1
  10. data/ext/kreuzberg_rb/extconf.rb +5 -35
  11. data/ext/kreuzberg_rb/native/Cargo.toml +16 -55
  12. data/ext/kreuzberg_rb/native/build.rs +14 -12
  13. data/ext/kreuzberg_rb/native/include/ieeefp.h +1 -1
  14. data/ext/kreuzberg_rb/native/include/msvc_compat/strings.h +1 -1
  15. data/ext/kreuzberg_rb/native/include/strings.h +2 -2
  16. data/ext/kreuzberg_rb/native/include/unistd.h +1 -1
  17. data/ext/kreuzberg_rb/native/src/lib.rs +34 -897
  18. data/extconf.rb +6 -38
  19. data/kreuzberg.gemspec +20 -114
  20. data/lib/kreuzberg/api_proxy.rb +18 -2
  21. data/lib/kreuzberg/cache_api.rb +0 -22
  22. data/lib/kreuzberg/cli.rb +10 -2
  23. data/lib/kreuzberg/cli_proxy.rb +10 -0
  24. data/lib/kreuzberg/config.rb +22 -274
  25. data/lib/kreuzberg/errors.rb +7 -73
  26. data/lib/kreuzberg/extraction_api.rb +8 -237
  27. data/lib/kreuzberg/mcp_proxy.rb +11 -2
  28. data/lib/kreuzberg/ocr_backend_protocol.rb +73 -0
  29. data/lib/kreuzberg/post_processor_protocol.rb +71 -0
  30. data/lib/kreuzberg/result.rb +33 -151
  31. data/lib/kreuzberg/setup_lib_path.rb +2 -22
  32. data/lib/kreuzberg/validator_protocol.rb +73 -0
  33. data/lib/kreuzberg/version.rb +1 -1
  34. data/lib/kreuzberg.rb +13 -27
  35. data/pkg/kreuzberg-4.0.0.rc1.gem +0 -0
  36. data/sig/kreuzberg.rbs +12 -105
  37. data/spec/binding/cache_spec.rb +22 -22
  38. data/spec/binding/cli_proxy_spec.rb +4 -2
  39. data/spec/binding/cli_spec.rb +11 -12
  40. data/spec/binding/config_spec.rb +0 -74
  41. data/spec/binding/config_validation_spec.rb +6 -100
  42. data/spec/binding/error_handling_spec.rb +97 -283
  43. data/spec/binding/plugins/ocr_backend_spec.rb +8 -8
  44. data/spec/binding/plugins/postprocessor_spec.rb +11 -11
  45. data/spec/binding/plugins/validator_spec.rb +13 -12
  46. data/spec/examples.txt +104 -0
  47. data/spec/fixtures/config.toml +1 -0
  48. data/spec/fixtures/config.yaml +1 -0
  49. data/spec/fixtures/invalid_config.toml +1 -0
  50. data/spec/smoke/package_spec.rb +3 -2
  51. data/spec/spec_helper.rb +3 -1
  52. data/vendor/kreuzberg/Cargo.toml +67 -192
  53. data/vendor/kreuzberg/README.md +9 -97
  54. data/vendor/kreuzberg/build.rs +194 -516
  55. data/vendor/kreuzberg/src/api/handlers.rs +9 -130
  56. data/vendor/kreuzberg/src/api/mod.rs +3 -18
  57. data/vendor/kreuzberg/src/api/server.rs +71 -236
  58. data/vendor/kreuzberg/src/api/types.rs +7 -43
  59. data/vendor/kreuzberg/src/bin/profile_extract.rs +455 -0
  60. data/vendor/kreuzberg/src/cache/mod.rs +3 -27
  61. data/vendor/kreuzberg/src/chunking/mod.rs +79 -1705
  62. data/vendor/kreuzberg/src/core/batch_mode.rs +0 -60
  63. data/vendor/kreuzberg/src/core/config.rs +23 -905
  64. data/vendor/kreuzberg/src/core/extractor.rs +106 -403
  65. data/vendor/kreuzberg/src/core/io.rs +2 -4
  66. data/vendor/kreuzberg/src/core/mime.rs +12 -2
  67. data/vendor/kreuzberg/src/core/mod.rs +3 -22
  68. data/vendor/kreuzberg/src/core/pipeline.rs +78 -395
  69. data/vendor/kreuzberg/src/embeddings.rs +21 -169
  70. data/vendor/kreuzberg/src/error.rs +2 -2
  71. data/vendor/kreuzberg/src/extraction/archive.rs +31 -36
  72. data/vendor/kreuzberg/src/extraction/docx.rs +1 -365
  73. data/vendor/kreuzberg/src/extraction/email.rs +11 -12
  74. data/vendor/kreuzberg/src/extraction/excel.rs +129 -138
  75. data/vendor/kreuzberg/src/extraction/html.rs +170 -1447
  76. data/vendor/kreuzberg/src/extraction/image.rs +14 -138
  77. data/vendor/kreuzberg/src/extraction/libreoffice.rs +3 -13
  78. data/vendor/kreuzberg/src/extraction/mod.rs +5 -21
  79. data/vendor/kreuzberg/src/extraction/office_metadata/mod.rs +0 -2
  80. data/vendor/kreuzberg/src/extraction/pandoc/batch.rs +275 -0
  81. data/vendor/kreuzberg/src/extraction/pandoc/mime_types.rs +178 -0
  82. data/vendor/kreuzberg/src/extraction/pandoc/mod.rs +491 -0
  83. data/vendor/kreuzberg/src/extraction/pandoc/server.rs +496 -0
  84. data/vendor/kreuzberg/src/extraction/pandoc/subprocess.rs +1188 -0
  85. data/vendor/kreuzberg/src/extraction/pandoc/version.rs +162 -0
  86. data/vendor/kreuzberg/src/extraction/pptx.rs +94 -196
  87. data/vendor/kreuzberg/src/extraction/structured.rs +4 -5
  88. data/vendor/kreuzberg/src/extraction/table.rs +1 -2
  89. data/vendor/kreuzberg/src/extraction/text.rs +10 -18
  90. data/vendor/kreuzberg/src/extractors/archive.rs +0 -22
  91. data/vendor/kreuzberg/src/extractors/docx.rs +148 -69
  92. data/vendor/kreuzberg/src/extractors/email.rs +9 -37
  93. data/vendor/kreuzberg/src/extractors/excel.rs +40 -81
  94. data/vendor/kreuzberg/src/extractors/html.rs +173 -182
  95. data/vendor/kreuzberg/src/extractors/image.rs +8 -32
  96. data/vendor/kreuzberg/src/extractors/mod.rs +10 -171
  97. data/vendor/kreuzberg/src/extractors/pandoc.rs +201 -0
  98. data/vendor/kreuzberg/src/extractors/pdf.rs +64 -329
  99. data/vendor/kreuzberg/src/extractors/pptx.rs +34 -79
  100. data/vendor/kreuzberg/src/extractors/structured.rs +0 -16
  101. data/vendor/kreuzberg/src/extractors/text.rs +7 -30
  102. data/vendor/kreuzberg/src/extractors/xml.rs +8 -27
  103. data/vendor/kreuzberg/src/keywords/processor.rs +1 -9
  104. data/vendor/kreuzberg/src/keywords/rake.rs +1 -0
  105. data/vendor/kreuzberg/src/language_detection/mod.rs +51 -94
  106. data/vendor/kreuzberg/src/lib.rs +5 -17
  107. data/vendor/kreuzberg/src/mcp/mod.rs +1 -4
  108. data/vendor/kreuzberg/src/mcp/server.rs +21 -145
  109. data/vendor/kreuzberg/src/ocr/mod.rs +0 -2
  110. data/vendor/kreuzberg/src/ocr/processor.rs +8 -19
  111. data/vendor/kreuzberg/src/ocr/tesseract_backend.rs +0 -2
  112. data/vendor/kreuzberg/src/pdf/error.rs +1 -93
  113. data/vendor/kreuzberg/src/pdf/metadata.rs +100 -263
  114. data/vendor/kreuzberg/src/pdf/mod.rs +2 -33
  115. data/vendor/kreuzberg/src/pdf/rendering.rs +12 -12
  116. data/vendor/kreuzberg/src/pdf/table.rs +64 -61
  117. data/vendor/kreuzberg/src/pdf/text.rs +24 -416
  118. data/vendor/kreuzberg/src/plugins/extractor.rs +8 -40
  119. data/vendor/kreuzberg/src/plugins/mod.rs +0 -3
  120. data/vendor/kreuzberg/src/plugins/ocr.rs +14 -22
  121. data/vendor/kreuzberg/src/plugins/processor.rs +1 -10
  122. data/vendor/kreuzberg/src/plugins/registry.rs +0 -15
  123. data/vendor/kreuzberg/src/plugins/validator.rs +8 -20
  124. data/vendor/kreuzberg/src/stopwords/mod.rs +2 -2
  125. data/vendor/kreuzberg/src/text/mod.rs +0 -8
  126. data/vendor/kreuzberg/src/text/quality.rs +15 -28
  127. data/vendor/kreuzberg/src/text/string_utils.rs +10 -22
  128. data/vendor/kreuzberg/src/text/token_reduction/core.rs +50 -86
  129. data/vendor/kreuzberg/src/text/token_reduction/filters.rs +16 -37
  130. data/vendor/kreuzberg/src/text/token_reduction/simd_text.rs +1 -2
  131. data/vendor/kreuzberg/src/types.rs +67 -907
  132. data/vendor/kreuzberg/src/utils/mod.rs +0 -14
  133. data/vendor/kreuzberg/src/utils/quality.rs +3 -12
  134. data/vendor/kreuzberg/tests/api_tests.rs +0 -506
  135. data/vendor/kreuzberg/tests/archive_integration.rs +0 -2
  136. data/vendor/kreuzberg/tests/batch_orchestration.rs +12 -57
  137. data/vendor/kreuzberg/tests/batch_processing.rs +8 -32
  138. data/vendor/kreuzberg/tests/chunking_offset_demo.rs +92 -0
  139. data/vendor/kreuzberg/tests/concurrency_stress.rs +8 -40
  140. data/vendor/kreuzberg/tests/config_features.rs +1 -33
  141. data/vendor/kreuzberg/tests/config_loading_tests.rs +39 -16
  142. data/vendor/kreuzberg/tests/core_integration.rs +9 -35
  143. data/vendor/kreuzberg/tests/csv_integration.rs +81 -71
  144. data/vendor/kreuzberg/tests/docx_metadata_extraction_test.rs +25 -23
  145. data/vendor/kreuzberg/tests/email_integration.rs +1 -3
  146. data/vendor/kreuzberg/tests/error_handling.rs +34 -43
  147. data/vendor/kreuzberg/tests/format_integration.rs +1 -7
  148. data/vendor/kreuzberg/tests/helpers/mod.rs +0 -60
  149. data/vendor/kreuzberg/tests/image_integration.rs +0 -2
  150. data/vendor/kreuzberg/tests/mime_detection.rs +16 -17
  151. data/vendor/kreuzberg/tests/ocr_configuration.rs +0 -4
  152. data/vendor/kreuzberg/tests/ocr_errors.rs +0 -22
  153. data/vendor/kreuzberg/tests/ocr_quality.rs +0 -2
  154. data/vendor/kreuzberg/tests/pandoc_integration.rs +503 -0
  155. data/vendor/kreuzberg/tests/pdf_integration.rs +0 -2
  156. data/vendor/kreuzberg/tests/pipeline_integration.rs +2 -36
  157. data/vendor/kreuzberg/tests/plugin_ocr_backend_test.rs +0 -5
  158. data/vendor/kreuzberg/tests/plugin_postprocessor_test.rs +1 -17
  159. data/vendor/kreuzberg/tests/plugin_system.rs +0 -6
  160. data/vendor/kreuzberg/tests/registry_integration_tests.rs +22 -2
  161. data/vendor/kreuzberg/tests/security_validation.rs +1 -13
  162. data/vendor/kreuzberg/tests/test_fastembed.rs +23 -45
  163. metadata +25 -171
  164. data/.rubocop.yml +0 -543
  165. data/ext/kreuzberg_rb/native/.cargo/config.toml +0 -23
  166. data/ext/kreuzberg_rb/native/Cargo.lock +0 -7619
  167. data/lib/kreuzberg/error_context.rb +0 -136
  168. data/lib/kreuzberg/types.rb +0 -170
  169. data/lib/libpdfium.so +0 -0
  170. data/spec/binding/async_operations_spec.rb +0 -473
  171. data/spec/binding/batch_operations_spec.rb +0 -595
  172. data/spec/binding/batch_spec.rb +0 -359
  173. data/spec/binding/config_result_spec.rb +0 -377
  174. data/spec/binding/embeddings_spec.rb +0 -816
  175. data/spec/binding/error_recovery_spec.rb +0 -488
  176. data/spec/binding/font_config_spec.rb +0 -220
  177. data/spec/binding/images_spec.rb +0 -738
  178. data/spec/binding/keywords_extraction_spec.rb +0 -600
  179. data/spec/binding/metadata_types_spec.rb +0 -1228
  180. data/spec/binding/pages_extraction_spec.rb +0 -471
  181. data/spec/binding/tables_spec.rb +0 -641
  182. data/spec/unit/config/chunking_config_spec.rb +0 -213
  183. data/spec/unit/config/embedding_config_spec.rb +0 -343
  184. data/spec/unit/config/extraction_config_spec.rb +0 -438
  185. data/spec/unit/config/font_config_spec.rb +0 -285
  186. data/spec/unit/config/hierarchy_config_spec.rb +0 -314
  187. data/spec/unit/config/image_extraction_config_spec.rb +0 -209
  188. data/spec/unit/config/image_preprocessing_config_spec.rb +0 -249
  189. data/spec/unit/config/keyword_config_spec.rb +0 -229
  190. data/spec/unit/config/language_detection_config_spec.rb +0 -258
  191. data/spec/unit/config/ocr_config_spec.rb +0 -171
  192. data/spec/unit/config/page_config_spec.rb +0 -221
  193. data/spec/unit/config/pdf_config_spec.rb +0 -267
  194. data/spec/unit/config/postprocessor_config_spec.rb +0 -290
  195. data/spec/unit/config/tesseract_config_spec.rb +0 -181
  196. data/spec/unit/config/token_reduction_config_spec.rb +0 -251
  197. data/test/metadata_types_test.rb +0 -959
  198. data/vendor/Cargo.toml +0 -61
  199. data/vendor/kreuzberg/examples/bench_fixes.rs +0 -71
  200. data/vendor/kreuzberg/examples/test_pdfium_fork.rs +0 -62
  201. data/vendor/kreuzberg/src/chunking/processor.rs +0 -219
  202. data/vendor/kreuzberg/src/core/batch_optimizations.rs +0 -385
  203. data/vendor/kreuzberg/src/core/config_validation.rs +0 -949
  204. data/vendor/kreuzberg/src/core/formats.rs +0 -235
  205. data/vendor/kreuzberg/src/core/server_config.rs +0 -1220
  206. data/vendor/kreuzberg/src/extraction/capacity.rs +0 -263
  207. data/vendor/kreuzberg/src/extraction/markdown.rs +0 -216
  208. data/vendor/kreuzberg/src/extraction/office_metadata/odt_properties.rs +0 -284
  209. data/vendor/kreuzberg/src/extractors/bibtex.rs +0 -470
  210. data/vendor/kreuzberg/src/extractors/docbook.rs +0 -504
  211. data/vendor/kreuzberg/src/extractors/epub.rs +0 -696
  212. data/vendor/kreuzberg/src/extractors/fictionbook.rs +0 -492
  213. data/vendor/kreuzberg/src/extractors/jats.rs +0 -1054
  214. data/vendor/kreuzberg/src/extractors/jupyter.rs +0 -368
  215. data/vendor/kreuzberg/src/extractors/latex.rs +0 -653
  216. data/vendor/kreuzberg/src/extractors/markdown.rs +0 -701
  217. data/vendor/kreuzberg/src/extractors/odt.rs +0 -628
  218. data/vendor/kreuzberg/src/extractors/opml.rs +0 -635
  219. data/vendor/kreuzberg/src/extractors/orgmode.rs +0 -529
  220. data/vendor/kreuzberg/src/extractors/rst.rs +0 -577
  221. data/vendor/kreuzberg/src/extractors/rtf.rs +0 -809
  222. data/vendor/kreuzberg/src/extractors/security.rs +0 -484
  223. data/vendor/kreuzberg/src/extractors/security_tests.rs +0 -367
  224. data/vendor/kreuzberg/src/extractors/typst.rs +0 -651
  225. data/vendor/kreuzberg/src/language_detection/processor.rs +0 -218
  226. data/vendor/kreuzberg/src/ocr/language_registry.rs +0 -520
  227. data/vendor/kreuzberg/src/panic_context.rs +0 -154
  228. data/vendor/kreuzberg/src/pdf/bindings.rs +0 -306
  229. data/vendor/kreuzberg/src/pdf/bundled.rs +0 -408
  230. data/vendor/kreuzberg/src/pdf/fonts.rs +0 -358
  231. data/vendor/kreuzberg/src/pdf/hierarchy.rs +0 -903
  232. data/vendor/kreuzberg/src/text/quality_processor.rs +0 -231
  233. data/vendor/kreuzberg/src/text/utf8_validation.rs +0 -193
  234. data/vendor/kreuzberg/src/utils/pool.rs +0 -503
  235. data/vendor/kreuzberg/src/utils/pool_sizing.rs +0 -364
  236. data/vendor/kreuzberg/src/utils/string_pool.rs +0 -761
  237. data/vendor/kreuzberg/tests/api_embed.rs +0 -360
  238. data/vendor/kreuzberg/tests/api_extract_multipart.rs +0 -52
  239. data/vendor/kreuzberg/tests/api_large_pdf_extraction.rs +0 -471
  240. data/vendor/kreuzberg/tests/api_large_pdf_extraction_diagnostics.rs +0 -289
  241. data/vendor/kreuzberg/tests/batch_pooling_benchmark.rs +0 -154
  242. data/vendor/kreuzberg/tests/bibtex_parity_test.rs +0 -421
  243. data/vendor/kreuzberg/tests/config_integration_test.rs +0 -753
  244. data/vendor/kreuzberg/tests/data/hierarchy_ground_truth.json +0 -294
  245. data/vendor/kreuzberg/tests/docbook_extractor_tests.rs +0 -500
  246. data/vendor/kreuzberg/tests/docx_vs_pandoc_comparison.rs +0 -370
  247. data/vendor/kreuzberg/tests/epub_native_extractor_tests.rs +0 -275
  248. data/vendor/kreuzberg/tests/fictionbook_extractor_tests.rs +0 -228
  249. data/vendor/kreuzberg/tests/html_table_test.rs +0 -551
  250. data/vendor/kreuzberg/tests/instrumentation_test.rs +0 -139
  251. data/vendor/kreuzberg/tests/jats_extractor_tests.rs +0 -639
  252. data/vendor/kreuzberg/tests/jupyter_extractor_tests.rs +0 -704
  253. data/vendor/kreuzberg/tests/latex_extractor_tests.rs +0 -496
  254. data/vendor/kreuzberg/tests/markdown_extractor_tests.rs +0 -490
  255. data/vendor/kreuzberg/tests/ocr_language_registry.rs +0 -191
  256. data/vendor/kreuzberg/tests/odt_extractor_tests.rs +0 -674
  257. data/vendor/kreuzberg/tests/opml_extractor_tests.rs +0 -616
  258. data/vendor/kreuzberg/tests/orgmode_extractor_tests.rs +0 -822
  259. data/vendor/kreuzberg/tests/page_markers.rs +0 -297
  260. data/vendor/kreuzberg/tests/pdf_hierarchy_detection.rs +0 -301
  261. data/vendor/kreuzberg/tests/pdf_hierarchy_quality.rs +0 -589
  262. data/vendor/kreuzberg/tests/pdf_ocr_triggering.rs +0 -301
  263. data/vendor/kreuzberg/tests/pdf_text_merging.rs +0 -475
  264. data/vendor/kreuzberg/tests/pdfium_linking.rs +0 -340
  265. data/vendor/kreuzberg/tests/rst_extractor_tests.rs +0 -694
  266. data/vendor/kreuzberg/tests/rtf_extractor_tests.rs +0 -775
  267. data/vendor/kreuzberg/tests/typst_behavioral_tests.rs +0 -1260
  268. data/vendor/kreuzberg/tests/typst_extractor_tests.rs +0 -648
  269. data/vendor/kreuzberg-ffi/Cargo.toml +0 -67
  270. data/vendor/kreuzberg-ffi/README.md +0 -851
  271. data/vendor/kreuzberg-ffi/benches/result_view_benchmark.rs +0 -227
  272. data/vendor/kreuzberg-ffi/build.rs +0 -168
  273. data/vendor/kreuzberg-ffi/cbindgen.toml +0 -37
  274. data/vendor/kreuzberg-ffi/kreuzberg-ffi.pc.in +0 -12
  275. data/vendor/kreuzberg-ffi/kreuzberg.h +0 -3012
  276. data/vendor/kreuzberg-ffi/src/batch_streaming.rs +0 -588
  277. data/vendor/kreuzberg-ffi/src/config.rs +0 -1341
  278. data/vendor/kreuzberg-ffi/src/error.rs +0 -901
  279. data/vendor/kreuzberg-ffi/src/extraction.rs +0 -555
  280. data/vendor/kreuzberg-ffi/src/helpers.rs +0 -879
  281. data/vendor/kreuzberg-ffi/src/lib.rs +0 -977
  282. data/vendor/kreuzberg-ffi/src/memory.rs +0 -493
  283. data/vendor/kreuzberg-ffi/src/mime.rs +0 -329
  284. data/vendor/kreuzberg-ffi/src/panic_shield.rs +0 -265
  285. data/vendor/kreuzberg-ffi/src/plugins/document_extractor.rs +0 -442
  286. data/vendor/kreuzberg-ffi/src/plugins/mod.rs +0 -14
  287. data/vendor/kreuzberg-ffi/src/plugins/ocr_backend.rs +0 -628
  288. data/vendor/kreuzberg-ffi/src/plugins/post_processor.rs +0 -438
  289. data/vendor/kreuzberg-ffi/src/plugins/validator.rs +0 -329
  290. data/vendor/kreuzberg-ffi/src/result.rs +0 -510
  291. data/vendor/kreuzberg-ffi/src/result_pool.rs +0 -639
  292. data/vendor/kreuzberg-ffi/src/result_view.rs +0 -773
  293. data/vendor/kreuzberg-ffi/src/string_intern.rs +0 -568
  294. data/vendor/kreuzberg-ffi/src/types.rs +0 -363
  295. data/vendor/kreuzberg-ffi/src/util.rs +0 -210
  296. data/vendor/kreuzberg-ffi/src/validation.rs +0 -848
  297. data/vendor/kreuzberg-ffi/tests.disabled/README.md +0 -48
  298. data/vendor/kreuzberg-ffi/tests.disabled/config_loading_tests.rs +0 -299
  299. data/vendor/kreuzberg-ffi/tests.disabled/config_tests.rs +0 -346
  300. data/vendor/kreuzberg-ffi/tests.disabled/extractor_tests.rs +0 -232
  301. data/vendor/kreuzberg-ffi/tests.disabled/plugin_registration_tests.rs +0 -470
  302. data/vendor/kreuzberg-tesseract/.commitlintrc.json +0 -13
  303. data/vendor/kreuzberg-tesseract/.crate-ignore +0 -2
  304. data/vendor/kreuzberg-tesseract/Cargo.lock +0 -2933
  305. data/vendor/kreuzberg-tesseract/Cargo.toml +0 -57
  306. data/vendor/kreuzberg-tesseract/LICENSE +0 -22
  307. data/vendor/kreuzberg-tesseract/README.md +0 -399
  308. data/vendor/kreuzberg-tesseract/build.rs +0 -1127
  309. data/vendor/kreuzberg-tesseract/patches/README.md +0 -71
  310. data/vendor/kreuzberg-tesseract/patches/tesseract.diff +0 -199
  311. data/vendor/kreuzberg-tesseract/src/api.rs +0 -1371
  312. data/vendor/kreuzberg-tesseract/src/choice_iterator.rs +0 -77
  313. data/vendor/kreuzberg-tesseract/src/enums.rs +0 -297
  314. data/vendor/kreuzberg-tesseract/src/error.rs +0 -81
  315. data/vendor/kreuzberg-tesseract/src/lib.rs +0 -145
  316. data/vendor/kreuzberg-tesseract/src/monitor.rs +0 -57
  317. data/vendor/kreuzberg-tesseract/src/mutable_iterator.rs +0 -197
  318. data/vendor/kreuzberg-tesseract/src/page_iterator.rs +0 -253
  319. data/vendor/kreuzberg-tesseract/src/result_iterator.rs +0 -286
  320. data/vendor/kreuzberg-tesseract/src/result_renderer.rs +0 -183
  321. data/vendor/kreuzberg-tesseract/tests/integration_test.rs +0 -211
@@ -1,674 +0,0 @@
1
- //! Comprehensive TDD test suite for ODT (OpenDocument Text) extraction
2
- //!
3
- //! This test suite validates ODT extraction capabilities using Pandoc's output as the baseline.
4
- //! It covers:
5
- //! - Metadata extraction (title, creator, date, keywords from meta.xml)
6
- //! - Content extraction (text, formatting, structure)
7
- //! - Table extraction with captions
8
- //! - Formatting preservation (bold, italic, strikeout)
9
- //! - Image handling with captions
10
- //! - Math formula extraction
11
- //! - Note handling (footnotes, endnotes)
12
- //! - Citation/reference extraction
13
- //! - Unicode and special character handling
14
- //!
15
- //! Note: These tests require the `office` feature to be enabled and Pandoc to be installed.
16
-
17
- #![cfg(feature = "office")]
18
-
19
- use kreuzberg::core::config::ExtractionConfig;
20
- use kreuzberg::core::extractor::extract_file;
21
- use std::path::{Path, PathBuf};
22
-
23
- mod helpers;
24
-
25
- /// Helper function to get the workspace root and construct test file paths
26
- fn get_test_file_path(filename: &str) -> PathBuf {
27
- let workspace_root = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
28
- .parent()
29
- .unwrap()
30
- .parent()
31
- .unwrap();
32
- workspace_root.join(format!("test_documents/odt/{}", filename))
33
- }
34
-
35
- /// Helper to verify a test file exists before running test
36
- fn ensure_test_file_exists(path: &Path) -> bool {
37
- if !path.exists() {
38
- println!("Skipping test: Test file not found at {:?}", path);
39
- false
40
- } else {
41
- true
42
- }
43
- }
44
-
45
- /// Tests extraction of document metadata from ODT meta.xml
46
- /// Validates: title, subject, creator, dates, generator
47
- #[tokio::test]
48
- async fn test_odt_metadata_extraction() {
49
- let workspace_root = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
50
- .parent()
51
- .unwrap()
52
- .parent()
53
- .unwrap();
54
- let test_file = workspace_root.join("test_documents/metadata_test.odt");
55
-
56
- if !ensure_test_file_exists(&test_file) {
57
- println!("Skipping metadata test: metadata_test.odt not found");
58
- return;
59
- }
60
-
61
- let config = ExtractionConfig::default();
62
- let result = extract_file(&test_file, None, &config)
63
- .await
64
- .expect("Should extract ODT metadata successfully");
65
-
66
- assert!(!result.content.is_empty(), "Content should not be empty");
67
- assert!(
68
- result.content.contains("Test Document"),
69
- "Should contain document title in content"
70
- );
71
-
72
- let metadata = &result.metadata.additional;
73
- println!("Extracted metadata: {:?}", metadata);
74
-
75
- if let Some(title) = metadata.get("title") {
76
- assert_eq!(title.as_str(), Some("Test Metadata Document"), "Title should match");
77
- }
78
-
79
- if let Some(subject) = metadata.get("subject") {
80
- assert_eq!(
81
- subject.as_str(),
82
- Some("Testing ODT Metadata Extraction"),
83
- "Subject should match"
84
- );
85
- }
86
-
87
- if let Some(created_by) = metadata.get("created_by") {
88
- assert_eq!(created_by.as_str(), Some("John Doe"), "Creator should match");
89
- }
90
-
91
- if let Some(authors) = metadata.get("authors") {
92
- let authors_array = authors.as_array().expect("Authors should be an array");
93
- assert_eq!(authors_array.len(), 1, "Should have one author");
94
- assert_eq!(authors_array[0].as_str(), Some("John Doe"), "Author name should match");
95
- }
96
-
97
- assert!(metadata.get("created_at").is_some(), "Creation date should be present");
98
-
99
- assert!(
100
- metadata.get("modified_at").is_some(),
101
- "Modification date should be present"
102
- );
103
-
104
- if let Some(generator) = metadata.get("generator") {
105
- let gen_str = generator.as_str().expect("Generator should be a string");
106
- assert!(gen_str.contains("Pandoc"), "Generator should be Pandoc");
107
- }
108
-
109
- println!("✅ ODT metadata extraction test passed!");
110
- println!(" Metadata fields extracted: {}", metadata.len());
111
- }
112
-
113
- /// Tests extraction of tables with captions from ODT
114
- /// Baseline from Pandoc: simpleTableWithCaption.odt
115
- /// Expected Pandoc output:
116
- /// ```
117
- /// --------- --------------
118
- /// Content More content
119
- /// --------- --------------
120
- /// : Table 1: Some caption for a table
121
- /// ```
122
- #[tokio::test]
123
- async fn test_odt_table_with_caption_extraction() {
124
- let test_file = get_test_file_path("simpleTableWithCaption.odt");
125
- if !ensure_test_file_exists(&test_file) {
126
- return;
127
- }
128
-
129
- let config = ExtractionConfig::default();
130
- let result = extract_file(&test_file, None, &config).await;
131
-
132
- if let Ok(result) = result {
133
- if !result.content.is_empty() {
134
- let content_lower = result.content.to_lowercase();
135
- assert!(
136
- content_lower.contains("content") || content_lower.contains("table") || !result.tables.is_empty(),
137
- "Should either extract table content or structured tables"
138
- );
139
- }
140
- println!("✅ ODT table with caption extraction test passed!");
141
- println!(" Extracted {} tables", result.tables.len());
142
- } else {
143
- println!("⚠️ ODT table extraction not fully supported yet (Pandoc integration needed)");
144
- }
145
- }
146
-
147
- /// Tests extraction of basic tables without captions
148
- /// Baseline from Pandoc: simpleTable.odt
149
- /// Expected: Table with "Content" and "More content" cells
150
- #[tokio::test]
151
- async fn test_odt_simple_table_extraction() {
152
- let test_file = get_test_file_path("simpleTable.odt");
153
- if !ensure_test_file_exists(&test_file) {
154
- return;
155
- }
156
-
157
- let config = ExtractionConfig::default();
158
- let result = extract_file(&test_file, None, &config).await;
159
-
160
- if let Ok(result) = result {
161
- if !result.content.is_empty() {
162
- let content_lower = result.content.to_lowercase();
163
- assert!(
164
- content_lower.contains("content") || !result.tables.is_empty(),
165
- "Table should either contain 'content' text or be in structured tables"
166
- );
167
- }
168
- println!("✅ ODT simple table extraction test passed!");
169
- } else {
170
- println!("⚠️ ODT table extraction not fully supported yet");
171
- }
172
- }
173
-
174
- /// Tests extraction of document heading hierarchy
175
- /// Baseline from Pandoc: headers.odt
176
- /// Expected:
177
- /// - H1: "A header (Lv 1)"
178
- /// - H2: "Another header (Lv 2)"
179
- /// - H1: "Back to Level 1"
180
- #[tokio::test]
181
- async fn test_odt_heading_structure_extraction() {
182
- let test_file = get_test_file_path("headers.odt");
183
- if !ensure_test_file_exists(&test_file) {
184
- return;
185
- }
186
-
187
- let config = ExtractionConfig::default();
188
- let result = extract_file(&test_file, None, &config)
189
- .await
190
- .expect("Should extract heading structure successfully");
191
-
192
- assert!(!result.content.is_empty(), "Content should not be empty");
193
-
194
- assert!(
195
- result.content.contains("header") || result.content.contains("Header"),
196
- "Should contain heading text"
197
- );
198
-
199
- assert!(
200
- result.content.contains("#") || result.content.contains("header"),
201
- "Should indicate heading structure"
202
- );
203
-
204
- println!("✅ ODT heading structure extraction test passed!");
205
- }
206
-
207
- /// Tests extraction of bold text formatting
208
- /// Baseline from Pandoc: bold.odt
209
- /// Expected Pandoc output: "Here comes **bold** text"
210
- #[tokio::test]
211
- async fn test_odt_bold_formatting_extraction() {
212
- let test_file = get_test_file_path("bold.odt");
213
- if !ensure_test_file_exists(&test_file) {
214
- return;
215
- }
216
-
217
- let config = ExtractionConfig::default();
218
- let result = extract_file(&test_file, None, &config)
219
- .await
220
- .expect("Should extract bold formatting successfully");
221
-
222
- assert!(!result.content.is_empty(), "Content should not be empty");
223
-
224
- let content = result.content.to_lowercase();
225
- assert!(content.contains("bold"), "Should contain 'bold' text");
226
-
227
- assert!(
228
- result.content.contains("**bold**") || result.content.contains("bold"),
229
- "Should preserve bold text"
230
- );
231
-
232
- println!("✅ ODT bold formatting extraction test passed!");
233
- }
234
-
235
- /// Tests extraction of italic text formatting
236
- /// Baseline from Pandoc: italic.odt
237
- /// Expected Pandoc output: "Here comes *italic* text"
238
- #[tokio::test]
239
- async fn test_odt_italic_formatting_extraction() {
240
- let test_file = get_test_file_path("italic.odt");
241
- if !ensure_test_file_exists(&test_file) {
242
- return;
243
- }
244
-
245
- let config = ExtractionConfig::default();
246
- let result = extract_file(&test_file, None, &config)
247
- .await
248
- .expect("Should extract italic formatting successfully");
249
-
250
- assert!(!result.content.is_empty(), "Content should not be empty");
251
-
252
- let content = result.content.to_lowercase();
253
- assert!(content.contains("italic"), "Should contain 'italic' text");
254
-
255
- assert!(
256
- result.content.contains("*italic*") || result.content.contains("italic"),
257
- "Should preserve italic text"
258
- );
259
-
260
- println!("✅ ODT italic formatting extraction test passed!");
261
- }
262
-
263
- /// Tests extraction of strikeout/strikethrough text formatting
264
- /// Baseline from Pandoc: strikeout.odt
265
- /// Expected Pandoc output: "Here comes text that was ~~striken out~~."
266
- #[tokio::test]
267
- async fn test_odt_strikeout_formatting_extraction() {
268
- let test_file = get_test_file_path("strikeout.odt");
269
- if !ensure_test_file_exists(&test_file) {
270
- return;
271
- }
272
-
273
- let config = ExtractionConfig::default();
274
- let result = extract_file(&test_file, None, &config)
275
- .await
276
- .expect("Should extract strikeout formatting successfully");
277
-
278
- assert!(!result.content.is_empty(), "Content should not be empty");
279
-
280
- let content = result.content.to_lowercase();
281
- assert!(
282
- content.contains("strike") || content.contains("striken"),
283
- "Should contain strikeout text"
284
- );
285
-
286
- println!("✅ ODT strikeout formatting extraction test passed!");
287
- }
288
-
289
- /// Tests extraction of images with captions
290
- /// Baseline from Pandoc: imageWithCaption.odt
291
- /// Expected: Image reference with caption
292
- /// Expected Pandoc output:
293
- /// ```
294
- /// ![Image caption](Pictures/10000000000000FA000000FAD6A15225.jpg)
295
- /// {alt="Abbildung 1: Image caption" width="5.292cm" height="5.292cm"}
296
- /// ```
297
- #[tokio::test]
298
- async fn test_odt_image_with_caption_extraction() {
299
- let test_file = get_test_file_path("imageWithCaption.odt");
300
- if !ensure_test_file_exists(&test_file) {
301
- return;
302
- }
303
-
304
- let config = ExtractionConfig::default();
305
- let result = extract_file(&test_file, None, &config).await;
306
-
307
- if let Ok(result) = result {
308
- if !result.content.is_empty() {
309
- let content_lower = result.content.to_lowercase();
310
- assert!(
311
- content_lower.contains("image")
312
- || content_lower.contains("caption")
313
- || content_lower.contains("!")
314
- || result.images.is_some(),
315
- "Should reference image or caption or have extracted images"
316
- );
317
- }
318
- println!("✅ ODT image with caption extraction test passed!");
319
- } else {
320
- println!("⚠️ ODT image extraction not fully supported yet");
321
- }
322
- }
323
-
324
- /// Tests extraction of mathematical formulas
325
- /// Baseline from Pandoc: formula.odt
326
- /// Expected Pandoc output: "$$E = {m \\cdot c^{2}}$$"
327
- #[tokio::test]
328
- async fn test_odt_formula_extraction() {
329
- let test_file = get_test_file_path("formula.odt");
330
- if !ensure_test_file_exists(&test_file) {
331
- return;
332
- }
333
-
334
- let config = ExtractionConfig::default();
335
- let result = extract_file(&test_file, None, &config)
336
- .await
337
- .expect("Should extract formula successfully");
338
-
339
- assert!(!result.content.is_empty(), "Content should not be empty");
340
-
341
- let content = &result.content;
342
- assert!(
343
- content.contains("E") && (content.contains("m") || content.contains("$")),
344
- "Should extract formula content"
345
- );
346
-
347
- println!("✅ ODT formula extraction test passed!");
348
- }
349
-
350
- /// Tests extraction of footnotes
351
- /// Baseline from Pandoc: footnote.odt
352
- /// Expected Pandoc output:
353
- /// ```
354
- /// Some text[^1] with a footnote.
355
- ///
356
- /// [^1]: Footnote text
357
- /// ```
358
- #[tokio::test]
359
- async fn test_odt_footnote_extraction() {
360
- let test_file = get_test_file_path("footnote.odt");
361
- if !ensure_test_file_exists(&test_file) {
362
- return;
363
- }
364
-
365
- let config = ExtractionConfig::default();
366
- let result = extract_file(&test_file, None, &config)
367
- .await
368
- .expect("Should extract footnote successfully");
369
-
370
- assert!(!result.content.is_empty(), "Content should not be empty");
371
-
372
- let content_lower = result.content.to_lowercase();
373
- assert!(
374
- content_lower.contains("footnote") || content_lower.contains("[^"),
375
- "Should extract footnote"
376
- );
377
-
378
- println!("✅ ODT footnote extraction test passed!");
379
- }
380
-
381
- /// Tests extraction of endnotes
382
- /// Baseline from Pandoc: endnote.odt
383
- /// Expected: Endnote content with reference (similar to footnotes)
384
- #[tokio::test]
385
- async fn test_odt_endnote_extraction() {
386
- let test_file = get_test_file_path("endnote.odt");
387
- if !ensure_test_file_exists(&test_file) {
388
- return;
389
- }
390
-
391
- let config = ExtractionConfig::default();
392
- let result = extract_file(&test_file, None, &config)
393
- .await
394
- .expect("Should extract endnote successfully");
395
-
396
- assert!(!result.content.is_empty(), "Content should not be empty");
397
-
398
- let content_lower = result.content.to_lowercase();
399
- assert!(
400
- content_lower.contains("endnote") || content_lower.contains("[^"),
401
- "Should extract endnote"
402
- );
403
-
404
- println!("✅ ODT endnote extraction test passed!");
405
- }
406
-
407
- /// Tests extraction of citations and references
408
- /// Baseline from Pandoc: citation.odt
409
- /// Expected Pandoc output: "Some text[@Ex] with a citation."
410
- #[tokio::test]
411
- async fn test_odt_citation_extraction() {
412
- let test_file = get_test_file_path("citation.odt");
413
- if !ensure_test_file_exists(&test_file) {
414
- return;
415
- }
416
-
417
- let config = ExtractionConfig::default();
418
- let result = extract_file(&test_file, None, &config)
419
- .await
420
- .expect("Should extract citation successfully");
421
-
422
- assert!(!result.content.is_empty(), "Content should not be empty");
423
-
424
- let content_lower = result.content.to_lowercase();
425
- assert!(
426
- content_lower.contains("citation") || content_lower.contains("text") || content_lower.contains("@"),
427
- "Should extract citation"
428
- );
429
-
430
- println!("✅ ODT citation extraction test passed!");
431
- }
432
-
433
- /// Tests extraction of unicode characters and special symbols
434
- /// Baseline from Pandoc: unicode.odt
435
- /// Expected: Proper preservation of unicode characters
436
- /// Expected Pandoc output: ""'çӨ©¼вбФШöɵ"
437
- #[tokio::test]
438
- async fn test_odt_unicode_extraction() {
439
- let test_file = get_test_file_path("unicode.odt");
440
- if !ensure_test_file_exists(&test_file) {
441
- return;
442
- }
443
-
444
- let config = ExtractionConfig::default();
445
- let result = extract_file(&test_file, None, &config)
446
- .await
447
- .expect("Should extract unicode successfully");
448
-
449
- assert!(!result.content.is_empty(), "Content should not be empty");
450
-
451
- assert!(!result.content.is_empty(), "Should extract unicode content (not empty)");
452
-
453
- println!("✅ ODT unicode extraction test passed!");
454
- println!(" Extracted unicode content: {:?}", result.content);
455
- }
456
-
457
- /// Tests extraction of inline code formatting
458
- /// Baseline from Pandoc: inlinedCode.odt
459
- /// Expected Pandoc output: "Here comes `inlined code` text and `an another` one."
460
- #[tokio::test]
461
- async fn test_odt_inlined_code_extraction() {
462
- let test_file = get_test_file_path("inlinedCode.odt");
463
- if !ensure_test_file_exists(&test_file) {
464
- return;
465
- }
466
-
467
- let config = ExtractionConfig::default();
468
- let result = extract_file(&test_file, None, &config)
469
- .await
470
- .expect("Should extract inline code successfully");
471
-
472
- assert!(!result.content.is_empty(), "Content should not be empty");
473
-
474
- let content_lower = result.content.to_lowercase();
475
- assert!(
476
- content_lower.contains("code") || content_lower.contains("`"),
477
- "Should extract inline code"
478
- );
479
-
480
- println!("✅ ODT inline code extraction test passed!");
481
- }
482
-
483
- /// Tests extraction of paragraph structure and content
484
- /// Baseline from Pandoc: paragraph.odt
485
- /// Expected: Multiple paragraphs separated by blank lines
486
- #[tokio::test]
487
- async fn test_odt_paragraph_structure_extraction() {
488
- let test_file = get_test_file_path("paragraph.odt");
489
- if !ensure_test_file_exists(&test_file) {
490
- return;
491
- }
492
-
493
- let config = ExtractionConfig::default();
494
- let result = extract_file(&test_file, None, &config)
495
- .await
496
- .expect("Should extract paragraph structure successfully");
497
-
498
- assert!(!result.content.is_empty(), "Content should not be empty");
499
-
500
- let content_lower = result.content.to_lowercase();
501
- assert!(content_lower.contains("paragraph"), "Should contain paragraph text");
502
-
503
- let paragraph_count = result.content.split('\n').filter(|l| !l.is_empty()).count();
504
- assert!(paragraph_count >= 2, "Should extract multiple paragraphs");
505
-
506
- println!("✅ ODT paragraph structure extraction test passed!");
507
- println!(" Extracted {} paragraph segments", paragraph_count);
508
- }
509
-
510
- /// Integration test: Verify ODT extraction works with standard API
511
- #[tokio::test]
512
- async fn test_odt_extraction_api_integration() {
513
- let test_file = get_test_file_path("bold.odt");
514
- if !ensure_test_file_exists(&test_file) {
515
- return;
516
- }
517
-
518
- let config = ExtractionConfig::default();
519
- let result = extract_file(&test_file, None, &config)
520
- .await
521
- .expect("Should extract via standard API");
522
-
523
- assert!(!result.content.is_empty(), "Should have content");
524
- assert_eq!(result.mime_type, "application/vnd.oasis.opendocument.text");
525
-
526
- println!("✅ ODT extraction API integration test passed!");
527
- }
528
-
529
- /// Test error handling for non-existent files
530
- #[tokio::test]
531
- async fn test_odt_extraction_missing_file_handling() {
532
- let test_file = get_test_file_path("nonexistent.odt");
533
- let config = ExtractionConfig::default();
534
-
535
- let result = extract_file(&test_file, None, &config).await;
536
-
537
- assert!(result.is_err(), "Should return error for non-existent file");
538
-
539
- println!("✅ ODT extraction error handling test passed!");
540
- }
541
-
542
- /// Test extraction from multiple representative files
543
- #[tokio::test]
544
- async fn test_odt_extraction_variety() {
545
- let test_files = vec![
546
- "bold.odt",
547
- "italic.odt",
548
- "headers.odt",
549
- "simpleTable.odt",
550
- "footnote.odt",
551
- ];
552
-
553
- let config = ExtractionConfig::default();
554
- let mut successful_extractions = 0;
555
-
556
- for filename in &test_files {
557
- let test_file = get_test_file_path(filename);
558
- if !test_file.exists() {
559
- continue;
560
- }
561
-
562
- if let Ok(result) = extract_file(&test_file, None, &config).await
563
- && !result.content.is_empty()
564
- {
565
- successful_extractions += 1;
566
- }
567
- }
568
-
569
- assert!(
570
- successful_extractions >= 3,
571
- "Should successfully extract from at least 3 test files"
572
- );
573
-
574
- println!("✅ ODT extraction variety test passed!");
575
- println!(
576
- " Successfully extracted {} out of {} files",
577
- successful_extractions,
578
- test_files.len()
579
- );
580
- }
581
-
582
- /// Test that ODT table extraction doesn't include duplicate cell content
583
- /// This is a regression test for the bug where table cells were extracted twice:
584
- /// once as markdown tables and once as raw cell text
585
- #[tokio::test]
586
- async fn test_odt_table_no_duplicate_content() {
587
- let test_file = get_test_file_path("simpleTable.odt");
588
- if !ensure_test_file_exists(&test_file) {
589
- return;
590
- }
591
-
592
- let config = ExtractionConfig::default();
593
- let result = extract_file(&test_file, None, &config)
594
- .await
595
- .expect("Should extract table successfully");
596
-
597
- assert!(!result.content.is_empty(), "Content should not be empty");
598
-
599
- let content_count = result.content.matches("Content").count();
600
-
601
- println!(" 'Content' appears {} times in output", content_count);
602
- println!(" Content preview:\n{}", result.content);
603
-
604
- assert!(
605
- content_count <= 3,
606
- "Content should not appear excessively, indicating no duplicate table cell extraction"
607
- );
608
-
609
- println!("✅ ODT table no duplicate content test passed!");
610
- }
611
-
612
- /// Test comprehensive table extraction with headers, multiple rows, and tables
613
- /// Uses the extraction_test document created with pandoc to ensure complete content
614
- #[tokio::test]
615
- async fn test_odt_comprehensive_table_extraction() {
616
- let test_file = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
617
- .parent()
618
- .unwrap()
619
- .parent()
620
- .unwrap()
621
- .join("test_documents/extraction_test.odt");
622
-
623
- if !test_file.exists() {
624
- println!("⚠️ Test document not found at {:?}, skipping", test_file);
625
- return;
626
- }
627
-
628
- let config = ExtractionConfig::default();
629
- let result = extract_file(&test_file, None, &config)
630
- .await
631
- .expect("Should extract comprehensive table document successfully");
632
-
633
- assert!(!result.content.is_empty(), "Content should not be empty");
634
-
635
- assert!(result.content.contains("Comprehensive"), "Should contain heading");
636
- assert!(
637
- result.content.contains("First Section") || result.content.contains("First"),
638
- "Should contain first section"
639
- );
640
- assert!(
641
- result.content.contains("Second Section") || result.content.contains("Second"),
642
- "Should contain second section"
643
- );
644
- assert!(
645
- result.content.contains("Third Section") || result.content.contains("Third"),
646
- "Should contain third section"
647
- );
648
-
649
- assert!(
650
- result.content.contains("|"),
651
- "Should contain pipe characters for markdown tables"
652
- );
653
- assert!(result.content.contains("---"), "Should contain table separator");
654
-
655
- assert!(
656
- result.content.contains("Header 1") || result.content.contains("Cell 1A"),
657
- "Should contain table data"
658
- );
659
- assert!(
660
- result.content.contains("Product") || result.content.contains("Apple"),
661
- "Should contain second table data"
662
- );
663
-
664
- let cell_count = result.content.matches("Cell 1A").count();
665
- assert!(
666
- cell_count <= 2,
667
- "Cell content should not be heavily duplicated (found {} instances)",
668
- cell_count
669
- );
670
-
671
- println!("✅ ODT comprehensive table extraction test passed!");
672
- println!(" Extracted content length: {} chars", result.content.len());
673
- println!(" Tables found in output: {}", result.tables.len());
674
- }