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('&', "&");
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('&', "&")
105}