Skip to main content

easyofd_reader/
content_extractor.rs

1//! 内容抽取器。
2//!
3//! 对应 Java: org.ofdrw.reader.ContentExtractor
4//!
5//! 从 OFD 文档中抽取文本内容,支持可选的过滤器。
6
7use easyofd_core::OfdPage;
8
9/// 文本抽取过滤器函数类型。
10///
11/// 对应 Java: `org.ofdrw.reader.extractor.ExtractorFilter`
12///
13/// 接收文本内容,返回是否允许该文本通过过滤。
14/// 返回 `None` 表示过滤掉该文本,返回 `Some(text)` 表示允许。
15pub type ExtractorFilterFn = dyn Fn(&str) -> Option<String>;
16
17/// 内容抽取器,从已解析的 OFD 页面中抽取文本。
18///
19/// 对应 Java: `org.ofdrw.reader.ContentExtractor`
20///
21/// # 用法
22///
23/// ```rust,no_run
24/// use easyofd_reader::ContentExtractor;
25///
26/// # fn example(pages: &[easyofd_core::OfdPage]) {
27/// let extractor = ContentExtractor::new();
28/// let texts = extractor.get_page_content(pages, 1);
29/// # }
30/// ```
31#[derive(Default)]
32pub struct ContentExtractor {
33    /// 文本抽取过滤器。
34    filter: Option<Box<ExtractorFilterFn>>,
35}
36
37/// 文本抽取结果接收器的回调函数类型。
38///
39/// 对应 Java: `ContentExtractor.Receiver`
40pub type ReceiverFn = dyn FnMut(usize, &[String]);
41
42impl ContentExtractor {
43    /// 创建不带过滤器的内容抽取器。
44    ///
45    /// 对应 Java: `ContentExtractor(OFDReader)`
46    #[must_use]
47    pub fn new() -> Self {
48        Self { filter: None }
49    }
50
51    /// 创建带过滤器的内容抽取器。
52    ///
53    /// 对应 Java: `ContentExtractor(OFDReader, ExtractorFilter)`
54    #[must_use]
55    pub fn with_filter(filter: Box<ExtractorFilterFn>) -> Self {
56        Self {
57            filter: Some(filter),
58        }
59    }
60
61    /// 抽取指定页面内的所有文字。
62    ///
63    /// 对应 Java: `ContentExtractor.getPageContent(int pageNum)`
64    ///
65    /// `page_num` 从 1 开始。
66    #[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    /// 获取 OFD 内所有页面的文本内容。
76    ///
77    /// 对应 Java: `ContentExtractor.extractAll()`
78    #[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    /// 遍历所有页面,对每页文本调用回调。
89    ///
90    /// 对应 Java: `ContentExtractor.traverse(Receiver)`
91    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    /// 从单个页面提取文本。
101    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}