bionic_ebooks/
lib.rs

1use regex::Regex;
2use std::error::Error;
3use std::fs::File;
4use std::io::Read;
5use std::io::Write;
6use std::path::PathBuf;
7use xmltree::Element;
8use xmltree::XMLNode;
9use zip::ZipArchive;
10use zip::ZipWriter;
11
12type EpubArchive = ZipArchive<File>;
13type EpubWriter = ZipWriter<File>;
14
15pub struct EpubProcessor {
16    pub in_path: PathBuf,
17    pub in_zip: EpubArchive,
18    pub out_zip: EpubWriter,
19}
20
21impl EpubProcessor {
22    pub fn new(in_path: PathBuf, out_path: PathBuf) -> Result<Self, Box<dyn Error>> {
23        File::create(out_path.clone())?;
24
25        Ok(Self {
26            in_path: in_path.clone(),
27            in_zip: ZipArchive::new(File::open(in_path)?)?,
28            out_zip: ZipWriter::new(File::create(out_path)?),
29        })
30    }
31
32    pub fn process(&mut self) {
33        for i in 0..self.in_zip.len() {
34            let mut file = self.in_zip.by_index(i).unwrap();
35            let mut buf = Vec::new();
36            let _ = file.read_to_end(&mut buf);
37            let re = Regex::new(r".*html$").unwrap();
38            if re.is_match(file.name()) {
39                buf = modify_xml(&buf);
40            }
41
42            let options = zip::write::FileOptions::default()
43                .compression_method(zip::CompressionMethod::Stored);
44            self.out_zip.start_file(file.name(), options).unwrap();
45
46            let _ = self.out_zip.write(&buf);
47        }
48    }
49}
50
51fn modify_xml(buf: &[u8]) -> Vec<u8> {
52    let mut names_element = Element::parse(buf).unwrap();
53
54    mutate_text(&mut names_element);
55
56    let mut out_buf: Vec<u8> = vec![];
57    names_element.write(&mut out_buf).unwrap();
58    out_buf
59}
60
61fn mutate_text(element: &mut Element) {
62    for node in element.children.iter_mut() {
63        match node {
64            XMLNode::Element(ref mut elem) => {
65                if elem.name != "b" {
66                    mutate_text(elem)
67                }
68            }
69            XMLNode::Text(ref mut text) => {
70                let bionic: Vec<String> = text.split_whitespace().map(to_bionic).collect();
71                let joined = bionic.join(" ");
72
73                let bionic_string = format!("<bionic> {} </bionic>", joined);
74
75                let bionic_element = Element::parse(bionic_string.as_bytes()).unwrap();
76                *node = xmltree::XMLNode::Element(bionic_element);
77            }
78            _ => (),
79        }
80    }
81}
82
83fn to_bionic(word: &str) -> String {
84    let trimmed_word = word.trim().replace('&', "&amp;");
85    let chars: Vec<char> = trimmed_word.chars().collect();
86    let mid_point = chars.len() / 2;
87
88    if mid_point >= chars.len() || chars.is_empty() {
89        return trimmed_word;
90    }
91
92    if chars.len() == 1 {
93        return if chars[0].is_ascii() {
94            format!("<b>{} </b>", trimmed_word)
95        } else {
96            trimmed_word
97        };
98    }
99
100    let (bold, remaining) = chars.split_at(mid_point);
101    let bold_string = String::from_iter(bold);
102    let remaining_string = String::from_iter(remaining);
103
104    format!("<b>{}</b>{}", bold_string, remaining_string).replace('&', "&amp;")
105}