rig_core/loaders/epub/
text_processors.rs1use std::{convert::Infallible, error::Error};
2
3use quick_xml::Reader;
4use quick_xml::escape::{resolve_xml_entity, unescape_with};
5use quick_xml::events::Event;
6
7pub trait TextProcessor {
8 type Error: Error + 'static;
9
10 fn process(text: &str) -> Result<String, Self::Error>;
11}
12
13pub struct RawTextProcessor;
14
15impl TextProcessor for RawTextProcessor {
16 type Error = Infallible;
17
18 fn process(text: &str) -> Result<String, Self::Error> {
19 Ok(text.to_string())
20 }
21}
22
23#[derive(thiserror::Error, Debug)]
24pub enum XmlProcessingError {
25 #[error("XML parsing error: {0}")]
26 Xml(#[from] quick_xml::Error),
27
28 #[error("Failed to unescape XML entity: {0}")]
29 Encoding(#[from] quick_xml::encoding::EncodingError),
30
31 #[error("Invalid UTF-8 sequence: {0}")]
32 Utf8(#[from] std::string::FromUtf8Error),
33}
34
35pub struct StripXmlProcessor;
38
39impl TextProcessor for StripXmlProcessor {
40 type Error = XmlProcessingError;
41
42 fn process(xml: &str) -> Result<String, Self::Error> {
43 let mut reader = Reader::from_str(xml.trim());
44
45 let mut result = String::with_capacity(xml.len() / 2);
46 let mut last_was_text = false;
47 let mut text = String::new();
48
49 loop {
50 let event = reader.read_event()?;
51 match &event {
52 Event::Text(e) => {
53 text.push_str(&e.decode()?);
54 continue;
55 }
56 Event::GeneralRef(e) => {
57 let reference = format!("&{};", e.decode()?);
58 text.push_str(
59 &unescape_with(&reference, resolve_xml_entity)
60 .map_err(quick_xml::Error::from)?,
61 );
62 continue;
63 }
64 _ => {}
65 }
66
67 if !text.trim().is_empty() {
70 if last_was_text {
71 result.push(' ');
72 }
73 result.push_str(&text);
74 last_was_text = true;
75 }
76 text.clear();
77
78 match event {
80 Event::CData(e) => {
81 let text = String::from_utf8(e.into_inner().into_owned())?;
82 if !text.trim().is_empty() {
83 if last_was_text {
84 result.push(' ');
85 }
86 result.push_str(&text);
87 last_was_text = true;
88 }
89 }
90 Event::Eof => break,
91 _ => {
92 last_was_text = false;
93 }
94 }
95 }
96
97 Ok(result)
98 }
99}