kreuzberg 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.
- checksums.yaml +7 -0
- data/.gitignore +8 -0
- data/.rspec +3 -0
- data/.rubocop.yaml +534 -0
- data/Gemfile +9 -0
- data/Gemfile.lock +157 -0
- data/README.md +421 -0
- data/Rakefile +25 -0
- data/Steepfile +47 -0
- data/examples/async_patterns.rb +340 -0
- data/ext/kreuzberg_rb/extconf.rb +35 -0
- data/ext/kreuzberg_rb/native/Cargo.toml +36 -0
- data/ext/kreuzberg_rb/native/README.md +425 -0
- data/ext/kreuzberg_rb/native/build.rs +17 -0
- data/ext/kreuzberg_rb/native/include/ieeefp.h +11 -0
- data/ext/kreuzberg_rb/native/include/msvc_compat/strings.h +14 -0
- data/ext/kreuzberg_rb/native/include/strings.h +20 -0
- data/ext/kreuzberg_rb/native/include/unistd.h +47 -0
- data/ext/kreuzberg_rb/native/src/lib.rs +2939 -0
- data/extconf.rb +28 -0
- data/kreuzberg.gemspec +105 -0
- data/lib/kreuzberg/api_proxy.rb +142 -0
- data/lib/kreuzberg/cache_api.rb +45 -0
- data/lib/kreuzberg/cli.rb +55 -0
- data/lib/kreuzberg/cli_proxy.rb +127 -0
- data/lib/kreuzberg/config.rb +684 -0
- data/lib/kreuzberg/errors.rb +50 -0
- data/lib/kreuzberg/extraction_api.rb +84 -0
- data/lib/kreuzberg/mcp_proxy.rb +186 -0
- data/lib/kreuzberg/ocr_backend_protocol.rb +113 -0
- data/lib/kreuzberg/post_processor_protocol.rb +86 -0
- data/lib/kreuzberg/result.rb +216 -0
- data/lib/kreuzberg/setup_lib_path.rb +79 -0
- data/lib/kreuzberg/validator_protocol.rb +89 -0
- data/lib/kreuzberg/version.rb +5 -0
- data/lib/kreuzberg.rb +82 -0
- data/pkg/kreuzberg-4.0.0.rc1.gem +0 -0
- data/sig/kreuzberg/internal.rbs +184 -0
- data/sig/kreuzberg.rbs +468 -0
- data/spec/binding/cache_spec.rb +227 -0
- data/spec/binding/cli_proxy_spec.rb +87 -0
- data/spec/binding/cli_spec.rb +54 -0
- data/spec/binding/config_spec.rb +345 -0
- data/spec/binding/config_validation_spec.rb +283 -0
- data/spec/binding/error_handling_spec.rb +213 -0
- data/spec/binding/errors_spec.rb +66 -0
- data/spec/binding/plugins/ocr_backend_spec.rb +307 -0
- data/spec/binding/plugins/postprocessor_spec.rb +269 -0
- data/spec/binding/plugins/validator_spec.rb +274 -0
- data/spec/examples.txt +104 -0
- data/spec/fixtures/config.toml +39 -0
- data/spec/fixtures/config.yaml +42 -0
- data/spec/fixtures/invalid_config.toml +4 -0
- data/spec/smoke/package_spec.rb +178 -0
- data/spec/spec_helper.rb +42 -0
- data/vendor/kreuzberg/Cargo.toml +134 -0
- data/vendor/kreuzberg/README.md +175 -0
- data/vendor/kreuzberg/build.rs +460 -0
- data/vendor/kreuzberg/src/api/error.rs +81 -0
- data/vendor/kreuzberg/src/api/handlers.rs +199 -0
- data/vendor/kreuzberg/src/api/mod.rs +79 -0
- data/vendor/kreuzberg/src/api/server.rs +353 -0
- data/vendor/kreuzberg/src/api/types.rs +170 -0
- data/vendor/kreuzberg/src/bin/profile_extract.rs +455 -0
- data/vendor/kreuzberg/src/cache/mod.rs +1143 -0
- data/vendor/kreuzberg/src/chunking/mod.rs +677 -0
- data/vendor/kreuzberg/src/core/batch_mode.rs +35 -0
- data/vendor/kreuzberg/src/core/config.rs +1032 -0
- data/vendor/kreuzberg/src/core/extractor.rs +903 -0
- data/vendor/kreuzberg/src/core/io.rs +327 -0
- data/vendor/kreuzberg/src/core/mime.rs +615 -0
- data/vendor/kreuzberg/src/core/mod.rs +42 -0
- data/vendor/kreuzberg/src/core/pipeline.rs +906 -0
- data/vendor/kreuzberg/src/embeddings.rs +323 -0
- data/vendor/kreuzberg/src/error.rs +431 -0
- data/vendor/kreuzberg/src/extraction/archive.rs +954 -0
- data/vendor/kreuzberg/src/extraction/docx.rs +40 -0
- data/vendor/kreuzberg/src/extraction/email.rs +854 -0
- data/vendor/kreuzberg/src/extraction/excel.rs +688 -0
- data/vendor/kreuzberg/src/extraction/html.rs +553 -0
- data/vendor/kreuzberg/src/extraction/image.rs +368 -0
- data/vendor/kreuzberg/src/extraction/libreoffice.rs +564 -0
- data/vendor/kreuzberg/src/extraction/mod.rs +77 -0
- data/vendor/kreuzberg/src/extraction/office_metadata/app_properties.rs +398 -0
- data/vendor/kreuzberg/src/extraction/office_metadata/core_properties.rs +247 -0
- data/vendor/kreuzberg/src/extraction/office_metadata/custom_properties.rs +240 -0
- data/vendor/kreuzberg/src/extraction/office_metadata/mod.rs +128 -0
- data/vendor/kreuzberg/src/extraction/pandoc/batch.rs +275 -0
- data/vendor/kreuzberg/src/extraction/pandoc/mime_types.rs +178 -0
- data/vendor/kreuzberg/src/extraction/pandoc/mod.rs +491 -0
- data/vendor/kreuzberg/src/extraction/pandoc/server.rs +496 -0
- data/vendor/kreuzberg/src/extraction/pandoc/subprocess.rs +1188 -0
- data/vendor/kreuzberg/src/extraction/pandoc/version.rs +162 -0
- data/vendor/kreuzberg/src/extraction/pptx.rs +3000 -0
- data/vendor/kreuzberg/src/extraction/structured.rs +490 -0
- data/vendor/kreuzberg/src/extraction/table.rs +328 -0
- data/vendor/kreuzberg/src/extraction/text.rs +269 -0
- data/vendor/kreuzberg/src/extraction/xml.rs +333 -0
- data/vendor/kreuzberg/src/extractors/archive.rs +425 -0
- data/vendor/kreuzberg/src/extractors/docx.rs +479 -0
- data/vendor/kreuzberg/src/extractors/email.rs +129 -0
- data/vendor/kreuzberg/src/extractors/excel.rs +344 -0
- data/vendor/kreuzberg/src/extractors/html.rs +410 -0
- data/vendor/kreuzberg/src/extractors/image.rs +195 -0
- data/vendor/kreuzberg/src/extractors/mod.rs +268 -0
- data/vendor/kreuzberg/src/extractors/pandoc.rs +201 -0
- data/vendor/kreuzberg/src/extractors/pdf.rs +496 -0
- data/vendor/kreuzberg/src/extractors/pptx.rs +234 -0
- data/vendor/kreuzberg/src/extractors/structured.rs +126 -0
- data/vendor/kreuzberg/src/extractors/text.rs +242 -0
- data/vendor/kreuzberg/src/extractors/xml.rs +128 -0
- data/vendor/kreuzberg/src/image/dpi.rs +164 -0
- data/vendor/kreuzberg/src/image/mod.rs +6 -0
- data/vendor/kreuzberg/src/image/preprocessing.rs +417 -0
- data/vendor/kreuzberg/src/image/resize.rs +89 -0
- data/vendor/kreuzberg/src/keywords/config.rs +154 -0
- data/vendor/kreuzberg/src/keywords/mod.rs +237 -0
- data/vendor/kreuzberg/src/keywords/processor.rs +267 -0
- data/vendor/kreuzberg/src/keywords/rake.rs +294 -0
- data/vendor/kreuzberg/src/keywords/types.rs +68 -0
- data/vendor/kreuzberg/src/keywords/yake.rs +163 -0
- data/vendor/kreuzberg/src/language_detection/mod.rs +942 -0
- data/vendor/kreuzberg/src/lib.rs +102 -0
- data/vendor/kreuzberg/src/mcp/mod.rs +32 -0
- data/vendor/kreuzberg/src/mcp/server.rs +1966 -0
- data/vendor/kreuzberg/src/ocr/cache.rs +469 -0
- data/vendor/kreuzberg/src/ocr/error.rs +37 -0
- data/vendor/kreuzberg/src/ocr/hocr.rs +216 -0
- data/vendor/kreuzberg/src/ocr/mod.rs +58 -0
- data/vendor/kreuzberg/src/ocr/processor.rs +847 -0
- data/vendor/kreuzberg/src/ocr/table/mod.rs +4 -0
- data/vendor/kreuzberg/src/ocr/table/tsv_parser.rs +144 -0
- data/vendor/kreuzberg/src/ocr/tesseract_backend.rs +450 -0
- data/vendor/kreuzberg/src/ocr/types.rs +393 -0
- data/vendor/kreuzberg/src/ocr/utils.rs +47 -0
- data/vendor/kreuzberg/src/ocr/validation.rs +206 -0
- data/vendor/kreuzberg/src/pdf/error.rs +122 -0
- data/vendor/kreuzberg/src/pdf/images.rs +139 -0
- data/vendor/kreuzberg/src/pdf/metadata.rs +346 -0
- data/vendor/kreuzberg/src/pdf/mod.rs +50 -0
- data/vendor/kreuzberg/src/pdf/rendering.rs +369 -0
- data/vendor/kreuzberg/src/pdf/table.rs +420 -0
- data/vendor/kreuzberg/src/pdf/text.rs +161 -0
- data/vendor/kreuzberg/src/plugins/extractor.rs +1010 -0
- data/vendor/kreuzberg/src/plugins/mod.rs +209 -0
- data/vendor/kreuzberg/src/plugins/ocr.rs +629 -0
- data/vendor/kreuzberg/src/plugins/processor.rs +641 -0
- data/vendor/kreuzberg/src/plugins/registry.rs +1324 -0
- data/vendor/kreuzberg/src/plugins/traits.rs +258 -0
- data/vendor/kreuzberg/src/plugins/validator.rs +955 -0
- data/vendor/kreuzberg/src/stopwords/mod.rs +1470 -0
- data/vendor/kreuzberg/src/text/mod.rs +19 -0
- data/vendor/kreuzberg/src/text/quality.rs +697 -0
- data/vendor/kreuzberg/src/text/string_utils.rs +217 -0
- data/vendor/kreuzberg/src/text/token_reduction/cjk_utils.rs +164 -0
- data/vendor/kreuzberg/src/text/token_reduction/config.rs +100 -0
- data/vendor/kreuzberg/src/text/token_reduction/core.rs +796 -0
- data/vendor/kreuzberg/src/text/token_reduction/filters.rs +902 -0
- data/vendor/kreuzberg/src/text/token_reduction/mod.rs +160 -0
- data/vendor/kreuzberg/src/text/token_reduction/semantic.rs +619 -0
- data/vendor/kreuzberg/src/text/token_reduction/simd_text.rs +147 -0
- data/vendor/kreuzberg/src/types.rs +873 -0
- data/vendor/kreuzberg/src/utils/mod.rs +17 -0
- data/vendor/kreuzberg/src/utils/quality.rs +959 -0
- data/vendor/kreuzberg/src/utils/string_utils.rs +381 -0
- data/vendor/kreuzberg/stopwords/af_stopwords.json +53 -0
- data/vendor/kreuzberg/stopwords/ar_stopwords.json +482 -0
- data/vendor/kreuzberg/stopwords/bg_stopwords.json +261 -0
- data/vendor/kreuzberg/stopwords/bn_stopwords.json +400 -0
- data/vendor/kreuzberg/stopwords/br_stopwords.json +1205 -0
- data/vendor/kreuzberg/stopwords/ca_stopwords.json +280 -0
- data/vendor/kreuzberg/stopwords/cs_stopwords.json +425 -0
- data/vendor/kreuzberg/stopwords/da_stopwords.json +172 -0
- data/vendor/kreuzberg/stopwords/de_stopwords.json +622 -0
- data/vendor/kreuzberg/stopwords/el_stopwords.json +849 -0
- data/vendor/kreuzberg/stopwords/en_stopwords.json +1300 -0
- data/vendor/kreuzberg/stopwords/eo_stopwords.json +175 -0
- data/vendor/kreuzberg/stopwords/es_stopwords.json +734 -0
- data/vendor/kreuzberg/stopwords/et_stopwords.json +37 -0
- data/vendor/kreuzberg/stopwords/eu_stopwords.json +100 -0
- data/vendor/kreuzberg/stopwords/fa_stopwords.json +801 -0
- data/vendor/kreuzberg/stopwords/fi_stopwords.json +849 -0
- data/vendor/kreuzberg/stopwords/fr_stopwords.json +693 -0
- data/vendor/kreuzberg/stopwords/ga_stopwords.json +111 -0
- data/vendor/kreuzberg/stopwords/gl_stopwords.json +162 -0
- data/vendor/kreuzberg/stopwords/gu_stopwords.json +226 -0
- data/vendor/kreuzberg/stopwords/ha_stopwords.json +41 -0
- data/vendor/kreuzberg/stopwords/he_stopwords.json +196 -0
- data/vendor/kreuzberg/stopwords/hi_stopwords.json +227 -0
- data/vendor/kreuzberg/stopwords/hr_stopwords.json +181 -0
- data/vendor/kreuzberg/stopwords/hu_stopwords.json +791 -0
- data/vendor/kreuzberg/stopwords/hy_stopwords.json +47 -0
- data/vendor/kreuzberg/stopwords/id_stopwords.json +760 -0
- data/vendor/kreuzberg/stopwords/it_stopwords.json +634 -0
- data/vendor/kreuzberg/stopwords/ja_stopwords.json +136 -0
- data/vendor/kreuzberg/stopwords/kn_stopwords.json +84 -0
- data/vendor/kreuzberg/stopwords/ko_stopwords.json +681 -0
- data/vendor/kreuzberg/stopwords/ku_stopwords.json +64 -0
- data/vendor/kreuzberg/stopwords/la_stopwords.json +51 -0
- data/vendor/kreuzberg/stopwords/lt_stopwords.json +476 -0
- data/vendor/kreuzberg/stopwords/lv_stopwords.json +163 -0
- data/vendor/kreuzberg/stopwords/ml_stopwords.json +1 -0
- data/vendor/kreuzberg/stopwords/mr_stopwords.json +101 -0
- data/vendor/kreuzberg/stopwords/ms_stopwords.json +477 -0
- data/vendor/kreuzberg/stopwords/ne_stopwords.json +490 -0
- data/vendor/kreuzberg/stopwords/nl_stopwords.json +415 -0
- data/vendor/kreuzberg/stopwords/no_stopwords.json +223 -0
- data/vendor/kreuzberg/stopwords/pl_stopwords.json +331 -0
- data/vendor/kreuzberg/stopwords/pt_stopwords.json +562 -0
- data/vendor/kreuzberg/stopwords/ro_stopwords.json +436 -0
- data/vendor/kreuzberg/stopwords/ru_stopwords.json +561 -0
- data/vendor/kreuzberg/stopwords/si_stopwords.json +193 -0
- data/vendor/kreuzberg/stopwords/sk_stopwords.json +420 -0
- data/vendor/kreuzberg/stopwords/sl_stopwords.json +448 -0
- data/vendor/kreuzberg/stopwords/so_stopwords.json +32 -0
- data/vendor/kreuzberg/stopwords/st_stopwords.json +33 -0
- data/vendor/kreuzberg/stopwords/sv_stopwords.json +420 -0
- data/vendor/kreuzberg/stopwords/sw_stopwords.json +76 -0
- data/vendor/kreuzberg/stopwords/ta_stopwords.json +129 -0
- data/vendor/kreuzberg/stopwords/te_stopwords.json +54 -0
- data/vendor/kreuzberg/stopwords/th_stopwords.json +118 -0
- data/vendor/kreuzberg/stopwords/tl_stopwords.json +149 -0
- data/vendor/kreuzberg/stopwords/tr_stopwords.json +506 -0
- data/vendor/kreuzberg/stopwords/uk_stopwords.json +75 -0
- data/vendor/kreuzberg/stopwords/ur_stopwords.json +519 -0
- data/vendor/kreuzberg/stopwords/vi_stopwords.json +647 -0
- data/vendor/kreuzberg/stopwords/yo_stopwords.json +62 -0
- data/vendor/kreuzberg/stopwords/zh_stopwords.json +796 -0
- data/vendor/kreuzberg/stopwords/zu_stopwords.json +31 -0
- data/vendor/kreuzberg/tests/api_tests.rs +966 -0
- data/vendor/kreuzberg/tests/archive_integration.rs +543 -0
- data/vendor/kreuzberg/tests/batch_orchestration.rs +542 -0
- data/vendor/kreuzberg/tests/batch_processing.rs +304 -0
- data/vendor/kreuzberg/tests/chunking_offset_demo.rs +92 -0
- data/vendor/kreuzberg/tests/concurrency_stress.rs +509 -0
- data/vendor/kreuzberg/tests/config_features.rs +580 -0
- data/vendor/kreuzberg/tests/config_loading_tests.rs +439 -0
- data/vendor/kreuzberg/tests/core_integration.rs +493 -0
- data/vendor/kreuzberg/tests/csv_integration.rs +424 -0
- data/vendor/kreuzberg/tests/docx_metadata_extraction_test.rs +124 -0
- data/vendor/kreuzberg/tests/email_integration.rs +325 -0
- data/vendor/kreuzberg/tests/error_handling.rs +393 -0
- data/vendor/kreuzberg/tests/format_integration.rs +159 -0
- data/vendor/kreuzberg/tests/helpers/mod.rs +142 -0
- data/vendor/kreuzberg/tests/image_integration.rs +253 -0
- data/vendor/kreuzberg/tests/keywords_integration.rs +479 -0
- data/vendor/kreuzberg/tests/keywords_quality.rs +509 -0
- data/vendor/kreuzberg/tests/mime_detection.rs +428 -0
- data/vendor/kreuzberg/tests/ocr_configuration.rs +510 -0
- data/vendor/kreuzberg/tests/ocr_errors.rs +676 -0
- data/vendor/kreuzberg/tests/ocr_quality.rs +627 -0
- data/vendor/kreuzberg/tests/ocr_stress.rs +469 -0
- data/vendor/kreuzberg/tests/pandoc_integration.rs +503 -0
- data/vendor/kreuzberg/tests/pdf_integration.rs +43 -0
- data/vendor/kreuzberg/tests/pipeline_integration.rs +1412 -0
- data/vendor/kreuzberg/tests/plugin_ocr_backend_test.rs +771 -0
- data/vendor/kreuzberg/tests/plugin_postprocessor_test.rs +561 -0
- data/vendor/kreuzberg/tests/plugin_system.rs +921 -0
- data/vendor/kreuzberg/tests/plugin_validator_test.rs +783 -0
- data/vendor/kreuzberg/tests/registry_integration_tests.rs +607 -0
- data/vendor/kreuzberg/tests/security_validation.rs +404 -0
- data/vendor/kreuzberg/tests/stopwords_integration_test.rs +888 -0
- data/vendor/kreuzberg/tests/test_fastembed.rs +609 -0
- data/vendor/kreuzberg/tests/xlsx_metadata_extraction_test.rs +87 -0
- metadata +471 -0
|
@@ -0,0 +1,503 @@
|
|
|
1
|
+
//! Pandoc integration tests.
|
|
2
|
+
//!
|
|
3
|
+
//! Tests for Pandoc-based document extraction (RST, LaTeX, ODT, RTF).
|
|
4
|
+
//! Validates that Pandoc integration works when available and degrades gracefully when missing.
|
|
5
|
+
//!
|
|
6
|
+
//! Note: These tests require the `office` feature to be enabled.
|
|
7
|
+
|
|
8
|
+
#![cfg(feature = "office")]
|
|
9
|
+
|
|
10
|
+
use kreuzberg::core::config::ExtractionConfig;
|
|
11
|
+
use kreuzberg::core::extractor::extract_bytes;
|
|
12
|
+
use kreuzberg::extraction::pandoc::validate_pandoc_version;
|
|
13
|
+
|
|
14
|
+
mod helpers;
|
|
15
|
+
|
|
16
|
+
/// Check if Pandoc is installed and available.
|
|
17
|
+
async fn is_pandoc_available() -> bool {
|
|
18
|
+
validate_pandoc_version().await.is_ok()
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/// Test reStructuredText (RST) extraction.
|
|
22
|
+
#[tokio::test]
|
|
23
|
+
async fn test_rst_extraction() {
|
|
24
|
+
if !is_pandoc_available().await {
|
|
25
|
+
println!("Skipping test: Pandoc not installed");
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
let config = ExtractionConfig::default();
|
|
30
|
+
|
|
31
|
+
let rst_content = b"Title
|
|
32
|
+
=====
|
|
33
|
+
|
|
34
|
+
This is a paragraph in reStructuredText.
|
|
35
|
+
|
|
36
|
+
Section Heading
|
|
37
|
+
---------------
|
|
38
|
+
|
|
39
|
+
- Bullet point 1
|
|
40
|
+
- Bullet point 2
|
|
41
|
+
- Bullet point 3
|
|
42
|
+
|
|
43
|
+
**Bold text** and *italic text*.";
|
|
44
|
+
|
|
45
|
+
let result = extract_bytes(rst_content, "text/x-rst", &config).await;
|
|
46
|
+
|
|
47
|
+
assert!(result.is_ok(), "RST extraction should succeed");
|
|
48
|
+
let extraction = result.unwrap();
|
|
49
|
+
|
|
50
|
+
assert_eq!(extraction.mime_type, "text/x-rst");
|
|
51
|
+
|
|
52
|
+
assert!(!extraction.content.is_empty(), "Content should not be empty");
|
|
53
|
+
assert!(
|
|
54
|
+
extraction.chunks.is_none(),
|
|
55
|
+
"Chunks should be None without chunking config"
|
|
56
|
+
);
|
|
57
|
+
assert!(
|
|
58
|
+
extraction.detected_languages.is_none(),
|
|
59
|
+
"Language detection not enabled"
|
|
60
|
+
);
|
|
61
|
+
assert!(extraction.tables.is_empty(), "RST should not extract tables");
|
|
62
|
+
|
|
63
|
+
assert!(extraction.content.contains("Title"), "Should extract title");
|
|
64
|
+
assert!(
|
|
65
|
+
extraction.content.contains("paragraph"),
|
|
66
|
+
"Should extract paragraph text"
|
|
67
|
+
);
|
|
68
|
+
assert!(
|
|
69
|
+
extraction.content.contains("Section Heading"),
|
|
70
|
+
"Should extract section heading"
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
assert!(
|
|
74
|
+
extraction.content.contains("Bullet point 1") || extraction.content.contains("point 1"),
|
|
75
|
+
"Should extract bullet points"
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
assert!(
|
|
79
|
+
extraction.content.contains("Bold text") || extraction.content.contains("italic text"),
|
|
80
|
+
"Should extract formatted text content"
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
let content_lower = extraction.content.to_lowercase();
|
|
84
|
+
assert!(content_lower.contains("title"), "Should extract title");
|
|
85
|
+
assert!(content_lower.contains("section"), "Should extract section heading");
|
|
86
|
+
assert!(content_lower.contains("bullet"), "Should extract bullet list");
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/// Test LaTeX extraction.
|
|
90
|
+
#[tokio::test]
|
|
91
|
+
async fn test_latex_extraction() {
|
|
92
|
+
if !is_pandoc_available().await {
|
|
93
|
+
println!("Skipping test: Pandoc not installed");
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
let config = ExtractionConfig::default();
|
|
98
|
+
|
|
99
|
+
let latex_content = b"\\documentclass{article}
|
|
100
|
+
\\begin{document}
|
|
101
|
+
|
|
102
|
+
\\title{Test Document}
|
|
103
|
+
\\author{Test Author}
|
|
104
|
+
\\maketitle
|
|
105
|
+
|
|
106
|
+
\\section{Introduction}
|
|
107
|
+
|
|
108
|
+
This is a test LaTeX document with \\textbf{bold} and \\textit{italic} text.
|
|
109
|
+
|
|
110
|
+
\\subsection{Subsection}
|
|
111
|
+
|
|
112
|
+
Some content in a subsection.
|
|
113
|
+
|
|
114
|
+
\\end{document}";
|
|
115
|
+
|
|
116
|
+
let result = extract_bytes(latex_content, "application/x-latex", &config).await;
|
|
117
|
+
|
|
118
|
+
assert!(result.is_ok(), "LaTeX extraction should succeed");
|
|
119
|
+
let extraction = result.unwrap();
|
|
120
|
+
|
|
121
|
+
assert_eq!(extraction.mime_type, "application/x-latex");
|
|
122
|
+
|
|
123
|
+
assert!(!extraction.content.is_empty(), "Content should not be empty");
|
|
124
|
+
assert!(
|
|
125
|
+
extraction.chunks.is_none(),
|
|
126
|
+
"Chunks should be None without chunking config"
|
|
127
|
+
);
|
|
128
|
+
assert!(
|
|
129
|
+
extraction.detected_languages.is_none(),
|
|
130
|
+
"Language detection not enabled"
|
|
131
|
+
);
|
|
132
|
+
assert!(
|
|
133
|
+
extraction.tables.is_empty(),
|
|
134
|
+
"LaTeX should not extract tables in this test"
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
assert!(
|
|
138
|
+
extraction.content.contains("Test Document"),
|
|
139
|
+
"Should extract document title"
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
assert!(
|
|
143
|
+
extraction.content.contains("Introduction"),
|
|
144
|
+
"Should extract section heading"
|
|
145
|
+
);
|
|
146
|
+
assert!(
|
|
147
|
+
extraction.content.contains("Subsection"),
|
|
148
|
+
"Should extract subsection heading"
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
assert!(
|
|
152
|
+
extraction.content.contains("test LaTeX document"),
|
|
153
|
+
"Should extract paragraph text"
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
assert!(
|
|
157
|
+
!extraction.content.contains("\\textbf") && !extraction.content.contains("\\section"),
|
|
158
|
+
"LaTeX commands should be stripped, not included in output"
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/// Test OpenDocument Text (ODT) extraction.
|
|
163
|
+
#[tokio::test]
|
|
164
|
+
async fn test_odt_extraction() {
|
|
165
|
+
if !is_pandoc_available().await {
|
|
166
|
+
println!("Skipping test: Pandoc not installed");
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
let config = ExtractionConfig::default();
|
|
171
|
+
|
|
172
|
+
let invalid_odt = b"This is not a valid ODT file";
|
|
173
|
+
|
|
174
|
+
let result = extract_bytes(invalid_odt, "application/vnd.oasis.opendocument.text", &config).await;
|
|
175
|
+
|
|
176
|
+
assert!(result.is_err(), "Invalid ODT should fail gracefully");
|
|
177
|
+
|
|
178
|
+
let error = result.unwrap_err();
|
|
179
|
+
match error {
|
|
180
|
+
kreuzberg::KreuzbergError::Parsing { .. } => {}
|
|
181
|
+
kreuzberg::KreuzbergError::Io(_) => {}
|
|
182
|
+
other => panic!("Expected Parsing or Io error, got: {:?}", other),
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/// Test Rich Text Format (RTF) extraction.
|
|
187
|
+
#[tokio::test]
|
|
188
|
+
async fn test_rtf_extraction() {
|
|
189
|
+
if !is_pandoc_available().await {
|
|
190
|
+
println!("Skipping test: Pandoc not installed");
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
let config = ExtractionConfig::default();
|
|
195
|
+
|
|
196
|
+
let rtf_content = b"{\\rtf1\\ansi\\deff0
|
|
197
|
+
{\\fonttbl{\\f0 Times New Roman;}}
|
|
198
|
+
\\f0\\fs24 This is a test RTF document.\\par
|
|
199
|
+
\\b Bold text\\b0 and \\i italic text\\i0.\\par
|
|
200
|
+
}";
|
|
201
|
+
|
|
202
|
+
let result = extract_bytes(rtf_content, "application/rtf", &config).await;
|
|
203
|
+
|
|
204
|
+
assert!(result.is_ok(), "RTF extraction should succeed");
|
|
205
|
+
let extraction = result.unwrap();
|
|
206
|
+
|
|
207
|
+
assert_eq!(extraction.mime_type, "application/rtf");
|
|
208
|
+
|
|
209
|
+
assert!(!extraction.content.is_empty(), "Content should not be empty");
|
|
210
|
+
assert!(
|
|
211
|
+
extraction.chunks.is_none(),
|
|
212
|
+
"Chunks should be None without chunking config"
|
|
213
|
+
);
|
|
214
|
+
assert!(
|
|
215
|
+
extraction.detected_languages.is_none(),
|
|
216
|
+
"Language detection not enabled"
|
|
217
|
+
);
|
|
218
|
+
assert!(
|
|
219
|
+
extraction.tables.is_empty(),
|
|
220
|
+
"RTF should not extract tables in this test"
|
|
221
|
+
);
|
|
222
|
+
|
|
223
|
+
assert!(
|
|
224
|
+
extraction.content.contains("test RTF document"),
|
|
225
|
+
"Should extract main paragraph"
|
|
226
|
+
);
|
|
227
|
+
assert!(
|
|
228
|
+
extraction.content.contains("Bold text") || extraction.content.contains("Bold"),
|
|
229
|
+
"Should extract bold text"
|
|
230
|
+
);
|
|
231
|
+
assert!(
|
|
232
|
+
extraction.content.contains("italic text") || extraction.content.contains("italic"),
|
|
233
|
+
"Should extract italic text"
|
|
234
|
+
);
|
|
235
|
+
|
|
236
|
+
assert!(
|
|
237
|
+
!extraction.content.contains("\\rtf") && !extraction.content.contains("\\par"),
|
|
238
|
+
"RTF control codes should be stripped from output"
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/// Test graceful degradation when Pandoc is not installed.
|
|
243
|
+
#[tokio::test]
|
|
244
|
+
async fn test_pandoc_not_installed() {
|
|
245
|
+
let validation_result = validate_pandoc_version().await;
|
|
246
|
+
|
|
247
|
+
if validation_result.is_ok() {
|
|
248
|
+
println!("Pandoc is installed - skipping 'not installed' test");
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
assert!(
|
|
253
|
+
validation_result.is_err(),
|
|
254
|
+
"Should return error when Pandoc not installed"
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/// Test Pandoc conversion error handling.
|
|
259
|
+
#[tokio::test]
|
|
260
|
+
async fn test_pandoc_conversion_error() {
|
|
261
|
+
if !is_pandoc_available().await {
|
|
262
|
+
println!("Skipping test: Pandoc not installed");
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
let config = ExtractionConfig::default();
|
|
267
|
+
|
|
268
|
+
let malformed_rst = b"===\nThis is malformed\n===\n===";
|
|
269
|
+
|
|
270
|
+
let result = extract_bytes(malformed_rst, "text/x-rst", &config).await;
|
|
271
|
+
|
|
272
|
+
assert!(
|
|
273
|
+
result.is_ok() || result.is_err(),
|
|
274
|
+
"Should handle malformed content gracefully"
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/// Test EPUB extraction (ebook format).
|
|
279
|
+
#[tokio::test]
|
|
280
|
+
async fn test_epub_extraction() {
|
|
281
|
+
if !is_pandoc_available().await {
|
|
282
|
+
println!("Skipping test: Pandoc not installed");
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
let config = ExtractionConfig::default();
|
|
287
|
+
|
|
288
|
+
let invalid_epub = b"This is not a valid EPUB file";
|
|
289
|
+
|
|
290
|
+
let result = extract_bytes(invalid_epub, "application/epub+zip", &config).await;
|
|
291
|
+
|
|
292
|
+
assert!(result.is_err(), "Invalid EPUB should fail gracefully");
|
|
293
|
+
|
|
294
|
+
let error = result.unwrap_err();
|
|
295
|
+
match error {
|
|
296
|
+
kreuzberg::KreuzbergError::Parsing { .. } => {}
|
|
297
|
+
kreuzberg::KreuzbergError::Io(_) => {}
|
|
298
|
+
other => panic!("Expected Parsing or Io error for invalid EPUB, got: {:?}", other),
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/// Test Org mode extraction.
|
|
303
|
+
#[tokio::test]
|
|
304
|
+
async fn test_org_mode_extraction() {
|
|
305
|
+
if !is_pandoc_available().await {
|
|
306
|
+
println!("Skipping test: Pandoc not installed");
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
let config = ExtractionConfig::default();
|
|
311
|
+
|
|
312
|
+
let org_content = b"* Top Level Heading
|
|
313
|
+
|
|
314
|
+
This is a paragraph in Org mode.
|
|
315
|
+
|
|
316
|
+
** Second Level Heading
|
|
317
|
+
|
|
318
|
+
- Item 1
|
|
319
|
+
- Item 2
|
|
320
|
+
- Item 3
|
|
321
|
+
|
|
322
|
+
*bold text* and /italic text/";
|
|
323
|
+
|
|
324
|
+
let result = extract_bytes(org_content, "text/x-org", &config).await;
|
|
325
|
+
|
|
326
|
+
assert!(result.is_ok(), "Org mode extraction should succeed");
|
|
327
|
+
let extraction = result.unwrap();
|
|
328
|
+
|
|
329
|
+
assert!(!extraction.content.is_empty(), "Content should not be empty");
|
|
330
|
+
assert!(
|
|
331
|
+
extraction.chunks.is_none(),
|
|
332
|
+
"Chunks should be None without chunking config"
|
|
333
|
+
);
|
|
334
|
+
assert!(
|
|
335
|
+
extraction.detected_languages.is_none(),
|
|
336
|
+
"Language detection not enabled"
|
|
337
|
+
);
|
|
338
|
+
assert!(
|
|
339
|
+
extraction.tables.is_empty(),
|
|
340
|
+
"Org mode should not extract tables in this test"
|
|
341
|
+
);
|
|
342
|
+
|
|
343
|
+
assert!(
|
|
344
|
+
extraction.content.contains("Top Level") || extraction.content.contains("paragraph"),
|
|
345
|
+
"Org mode content should be extracted"
|
|
346
|
+
);
|
|
347
|
+
|
|
348
|
+
assert!(
|
|
349
|
+
extraction.content.contains("paragraph") || extraction.content.contains("Heading"),
|
|
350
|
+
"Text content should be present"
|
|
351
|
+
);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/// Test Typst extraction (new document format).
|
|
355
|
+
#[tokio::test]
|
|
356
|
+
async fn test_typst_extraction() {
|
|
357
|
+
if !is_pandoc_available().await {
|
|
358
|
+
println!("Skipping test: Pandoc not installed");
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
let config = ExtractionConfig::default();
|
|
363
|
+
|
|
364
|
+
let typst_content = b"= Heading
|
|
365
|
+
|
|
366
|
+
This is a paragraph in Typst.
|
|
367
|
+
|
|
368
|
+
== Subheading
|
|
369
|
+
|
|
370
|
+
#strong[Bold text] and #emph[italic text].";
|
|
371
|
+
|
|
372
|
+
let result = extract_bytes(typst_content, "application/x-typst", &config).await;
|
|
373
|
+
|
|
374
|
+
assert!(
|
|
375
|
+
result.is_ok() || result.is_err(),
|
|
376
|
+
"Should handle Typst gracefully (may not be supported in all Pandoc versions)"
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/// Test CommonMark extraction.
|
|
381
|
+
#[tokio::test]
|
|
382
|
+
async fn test_commonmark_extraction() {
|
|
383
|
+
if !is_pandoc_available().await {
|
|
384
|
+
println!("Skipping test: Pandoc not installed");
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
let config = ExtractionConfig::default();
|
|
389
|
+
|
|
390
|
+
let commonmark_content = b"# Heading
|
|
391
|
+
|
|
392
|
+
This is a paragraph in CommonMark.
|
|
393
|
+
|
|
394
|
+
## Subheading
|
|
395
|
+
|
|
396
|
+
- List item 1
|
|
397
|
+
- List item 2
|
|
398
|
+
|
|
399
|
+
**Bold** and *italic* text.";
|
|
400
|
+
|
|
401
|
+
let result = extract_bytes(commonmark_content, "text/x-commonmark", &config).await;
|
|
402
|
+
|
|
403
|
+
assert!(result.is_ok(), "CommonMark extraction should succeed");
|
|
404
|
+
let extraction = result.unwrap();
|
|
405
|
+
|
|
406
|
+
assert!(!extraction.content.is_empty(), "Content should not be empty");
|
|
407
|
+
assert!(
|
|
408
|
+
extraction.chunks.is_none(),
|
|
409
|
+
"Chunks should be None without chunking config"
|
|
410
|
+
);
|
|
411
|
+
assert!(
|
|
412
|
+
extraction.detected_languages.is_none(),
|
|
413
|
+
"Language detection not enabled"
|
|
414
|
+
);
|
|
415
|
+
assert!(
|
|
416
|
+
extraction.tables.is_empty(),
|
|
417
|
+
"CommonMark should not extract tables in this test"
|
|
418
|
+
);
|
|
419
|
+
|
|
420
|
+
assert!(
|
|
421
|
+
extraction.content.contains("Heading") || extraction.content.contains("paragraph"),
|
|
422
|
+
"CommonMark content should be extracted"
|
|
423
|
+
);
|
|
424
|
+
|
|
425
|
+
let content_lower = extraction.content.to_lowercase();
|
|
426
|
+
assert!(
|
|
427
|
+
content_lower.contains("heading") || content_lower.contains("paragraph"),
|
|
428
|
+
"Should extract text"
|
|
429
|
+
);
|
|
430
|
+
assert!(
|
|
431
|
+
content_lower.contains("list") || content_lower.contains("item"),
|
|
432
|
+
"Should extract list items"
|
|
433
|
+
);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/// Test empty content.
|
|
437
|
+
#[tokio::test]
|
|
438
|
+
async fn test_pandoc_empty_content() {
|
|
439
|
+
if !is_pandoc_available().await {
|
|
440
|
+
println!("Skipping test: Pandoc not installed");
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
let config = ExtractionConfig::default();
|
|
445
|
+
|
|
446
|
+
let empty_rst = b"";
|
|
447
|
+
|
|
448
|
+
let result = extract_bytes(empty_rst, "text/x-rst", &config).await;
|
|
449
|
+
|
|
450
|
+
if let Ok(extraction) = result {
|
|
451
|
+
assert!(
|
|
452
|
+
extraction.content.is_empty() || extraction.content.trim().is_empty(),
|
|
453
|
+
"Empty input should produce empty or minimal output"
|
|
454
|
+
);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/// Test Unicode content in Pandoc formats.
|
|
459
|
+
#[tokio::test]
|
|
460
|
+
async fn test_pandoc_unicode_content() {
|
|
461
|
+
if !is_pandoc_available().await {
|
|
462
|
+
println!("Skipping test: Pandoc not installed");
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
let config = ExtractionConfig::default();
|
|
467
|
+
|
|
468
|
+
let unicode_rst = "Title with Unicode
|
|
469
|
+
==================
|
|
470
|
+
|
|
471
|
+
This document contains Unicode: 你好世界 🌍 café
|
|
472
|
+
|
|
473
|
+
Section
|
|
474
|
+
-------
|
|
475
|
+
|
|
476
|
+
Arabic: مرحبا
|
|
477
|
+
Emoji: 🎉 ✅ 🚀"
|
|
478
|
+
.as_bytes();
|
|
479
|
+
|
|
480
|
+
let result = extract_bytes(unicode_rst, "text/x-rst", &config).await;
|
|
481
|
+
|
|
482
|
+
assert!(result.is_ok(), "Unicode content should be handled");
|
|
483
|
+
let extraction = result.unwrap();
|
|
484
|
+
|
|
485
|
+
assert!(!extraction.content.is_empty(), "Content should be extracted");
|
|
486
|
+
assert!(
|
|
487
|
+
extraction.chunks.is_none(),
|
|
488
|
+
"Chunks should be None without chunking config"
|
|
489
|
+
);
|
|
490
|
+
assert!(
|
|
491
|
+
extraction.detected_languages.is_none(),
|
|
492
|
+
"Language detection not enabled"
|
|
493
|
+
);
|
|
494
|
+
assert!(
|
|
495
|
+
extraction.tables.is_empty(),
|
|
496
|
+
"RST should not extract tables in this test"
|
|
497
|
+
);
|
|
498
|
+
|
|
499
|
+
assert!(
|
|
500
|
+
extraction.content.len() > 20,
|
|
501
|
+
"Should have substantial extracted content"
|
|
502
|
+
);
|
|
503
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
//! PDF integration tests that remain specific to the Rust core.
|
|
2
|
+
//!
|
|
3
|
+
//! Positive-path scenarios live in the shared fixtures that back the
|
|
4
|
+
//! multi-language E2E generator. This module keeps only the cases that
|
|
5
|
+
//! exercise Rust-specific failure handling or error propagation.
|
|
6
|
+
|
|
7
|
+
mod helpers;
|
|
8
|
+
|
|
9
|
+
use helpers::*;
|
|
10
|
+
use kreuzberg::core::config::ExtractionConfig;
|
|
11
|
+
use kreuzberg::extract_file_sync;
|
|
12
|
+
|
|
13
|
+
#[test]
|
|
14
|
+
fn test_pdf_password_protected_fails_gracefully() {
|
|
15
|
+
if skip_if_missing("pdfs/copy_protected.pdf") {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
let file_path = get_test_file_path("pdfs/copy_protected.pdf");
|
|
20
|
+
let result = extract_file_sync(&file_path, None, &ExtractionConfig::default());
|
|
21
|
+
|
|
22
|
+
match result {
|
|
23
|
+
Ok(extraction_result) => {
|
|
24
|
+
assert_mime_type(&extraction_result, "application/pdf");
|
|
25
|
+
assert!(
|
|
26
|
+
extraction_result.chunks.is_none(),
|
|
27
|
+
"Chunks should be None without chunking config"
|
|
28
|
+
);
|
|
29
|
+
assert!(
|
|
30
|
+
extraction_result.detected_languages.is_none(),
|
|
31
|
+
"Language detection not enabled"
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
Err(e) => {
|
|
35
|
+
let error_msg = e.to_string().to_lowercase();
|
|
36
|
+
assert!(
|
|
37
|
+
error_msg.contains("password") || error_msg.contains("protected") || error_msg.contains("encrypted"),
|
|
38
|
+
"Error message should indicate password/protection issue, got: {}",
|
|
39
|
+
e
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|