easyofd_reader/
content_extractor.rs1use easyofd_core::OfdPage;
8
9pub type ExtractorFilterFn = dyn Fn(&str) -> Option<String>;
16
17#[derive(Default)]
32pub struct ContentExtractor {
33 filter: Option<Box<ExtractorFilterFn>>,
35}
36
37pub type ReceiverFn = dyn FnMut(usize, &[String]);
41
42impl ContentExtractor {
43 #[must_use]
47 pub fn new() -> Self {
48 Self { filter: None }
49 }
50
51 #[must_use]
55 pub fn with_filter(filter: Box<ExtractorFilterFn>) -> Self {
56 Self {
57 filter: Some(filter),
58 }
59 }
60
61 #[must_use]
67 pub fn get_page_content(&self, pages: &[OfdPage], page_num: usize) -> Vec<String> {
68 if page_num == 0 || page_num > pages.len() {
69 return Vec::new();
70 }
71 let page = &pages[page_num - 1];
72 self.extract_page_texts(page)
73 }
74
75 #[must_use]
79 pub fn extract_all(&self, pages: &[OfdPage]) -> Vec<String> {
80 let mut all_texts = Vec::new();
81 for page in pages {
82 let page_texts = self.extract_page_texts(page);
83 all_texts.extend(page_texts);
84 }
85 all_texts
86 }
87
88 pub fn traverse(&self, pages: &[OfdPage], mut receiver: impl FnMut(usize, &[String])) {
92 for (i, page) in pages.iter().enumerate() {
93 let texts = self.extract_page_texts(page);
94 if !texts.is_empty() {
95 receiver(i + 1, &texts);
96 }
97 }
98 }
99
100 fn extract_page_texts(&self, page: &OfdPage) -> Vec<String> {
102 use easyofd_core::ContentObject;
103
104 let mut texts = Vec::new();
105 for obj in &page.content {
106 if let ContentObject::Text(t) = obj {
107 let text = &t.text;
108 if let Some(ref filter) = self.filter {
109 if let Some(allowed) = filter(text) {
110 if !allowed.is_empty() {
111 texts.push(allowed);
112 }
113 }
114 } else if !text.is_empty() {
115 texts.push(text.clone());
116 }
117 }
118 }
119 texts
120 }
121}
122
123#[cfg(test)]
124mod tests {
125 use super::*;
126 use easyofd_core::TextObject;
127
128 fn make_page_with_texts(texts: &[&str]) -> OfdPage {
129 let mut page = OfdPage::new(210.0, 297.0);
130 for text in texts {
131 page.add_text(TextObject::new(0.0, 0.0, *text));
132 }
133 page
134 }
135
136 #[test]
137 fn test_extract_single_page() {
138 let pages = vec![make_page_with_texts(&["Hello", "World"])];
139 let extractor = ContentExtractor::new();
140 let result = extractor.get_page_content(&pages, 1);
141 assert_eq!(result, vec!["Hello", "World"]);
142 }
143
144 #[test]
145 fn test_extract_page_out_of_range() {
146 let pages = vec![make_page_with_texts(&["Hello"])];
147 let extractor = ContentExtractor::new();
148 assert!(extractor.get_page_content(&pages, 0).is_empty());
149 assert!(extractor.get_page_content(&pages, 2).is_empty());
150 }
151
152 #[test]
153 fn test_extract_all() {
154 let pages = vec![
155 make_page_with_texts(&["Page1"]),
156 make_page_with_texts(&["Page2"]),
157 ];
158 let extractor = ContentExtractor::new();
159 let result = extractor.extract_all(&pages);
160 assert_eq!(result, vec!["Page1", "Page2"]);
161 }
162
163 #[test]
164 fn test_with_filter() {
165 let pages = vec![make_page_with_texts(&["Hello", "SECRET", "World"])];
166 let extractor = ContentExtractor::with_filter(Box::new(|text| {
167 if text.contains("SECRET") {
168 None
169 } else {
170 Some(text.to_string())
171 }
172 }));
173 let result = extractor.get_page_content(&pages, 1);
174 assert_eq!(result, vec!["Hello", "World"]);
175 }
176
177 #[test]
178 fn test_traverse() {
179 let pages = vec![make_page_with_texts(&["A"]), make_page_with_texts(&["B"])];
180 let extractor = ContentExtractor::new();
181 let mut visited = Vec::new();
182 extractor.traverse(&pages, |page_num, texts| {
183 visited.push((page_num, texts.to_vec()));
184 });
185 assert_eq!(visited.len(), 2);
186 assert_eq!(visited[0].0, 1);
187 assert_eq!(visited[1].0, 2);
188 }
189}