1use std::path::Path;
2
3pub fn extract_text(path: &Path) -> Result<String, String> {
5 let bytes = std::fs::read(path).map_err(|e| format!("Failed to read file: {}", e))?;
6 extract_text_from_bytes(&bytes)
7}
8
9pub fn extract_text_from_bytes(bytes: &[u8]) -> Result<String, String> {
11 let doc = pdf_oxide::PdfDocument::from_bytes(bytes.to_vec())
12 .map_err(|e| format!("Failed to open PDF: {}", e))?;
13 let text = doc
14 .extract_all_text()
15 .map_err(|e| format!("Failed to extract text: {}", e))?;
16 Ok(text)
17}
18
19pub fn extract_section_abstract(full_text: &str) -> Option<String> {
21 let lower = full_text.to_lowercase();
22 let start = lower.find("abstract")?;
23 let after_abstract = start + "abstract".len();
24 let section_headings = [
26 "introduction",
27 "background",
28 "related work",
29 "methods",
30 "methodology",
31 "results",
32 "discussion",
33 "conclusion",
34 ];
35 let end = section_headings
36 .iter()
37 .filter_map(|h| lower[after_abstract..].find(h).map(|i| after_abstract + i))
38 .min()
39 .unwrap_or(full_text.len());
40 let section = full_text[after_abstract..end].trim();
41 if section.is_empty() {
42 None
43 } else {
44 Some(section.to_string())
45 }
46}
47
48#[cfg(test)]
49mod tests {
50 use super::*;
51 use std::path::PathBuf;
52
53 fn fixture_path() -> PathBuf {
54 PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/test.pdf")
55 }
56
57 #[test]
59 fn extract_text_returns_content() {
60 let text = extract_text(&fixture_path()).unwrap();
61 assert!(!text.is_empty(), "extracted text should not be empty");
62 }
63
64 #[test]
65 fn extract_text_contains_expected_words() {
66 let text = extract_text(&fixture_path()).unwrap();
67 assert!(
68 text.contains("Transformer") || text.contains("attention") || text.contains("Attention"),
69 "text should contain expected words, got: {}",
70 &text[..text.len().min(200)]
71 );
72 }
73
74 #[test]
76 fn extract_section_abstract_returns_content() {
77 let text = extract_text(&fixture_path()).unwrap();
78 let abstract_text = extract_section_abstract(&text);
79 assert!(abstract_text.is_some(), "should find abstract section");
80 let abs = abstract_text.unwrap();
81 assert!(!abs.is_empty());
82 }
83
84 #[test]
85 fn extract_section_abstract_does_not_contain_introduction() {
86 let text = extract_text(&fixture_path()).unwrap();
87 let abs = extract_section_abstract(&text).unwrap();
88 let lower = abs.to_lowercase();
89 assert!(
90 !lower.contains("introduction"),
91 "abstract should not contain introduction heading, got: {}",
92 abs
93 );
94 }
95
96 #[test]
97 fn extract_text_from_bytes_works() {
98 let bytes = std::fs::read(fixture_path()).unwrap();
99 let text = extract_text_from_bytes(&bytes).unwrap();
100 assert!(!text.is_empty());
101 }
102
103 #[test]
104 fn extract_text_nonexistent_file_returns_err() {
105 let result = extract_text(Path::new("/nonexistent/fake.pdf"));
106 assert!(result.is_err());
107 }
108}