use std::fs;
use crate::{Error, Job, Result};
pub fn epub_to_html(job: &Job) -> Result<()> {
let mut doc = epub::doc::EpubDoc::new(job.input)
.map_err(|e| Error::other(format!("epub open: {e}")))?;
let mut out = String::from("<!doctype html><html><head>");
if let Some(title) = doc.mdata("title") {
out.push_str("<title>");
push_html_escaped(&mut out, &title);
out.push_str("</title>");
}
out.push_str("</head><body>");
let spine = doc.spine.clone();
for item in &spine {
if let Some((content, _mime)) = doc.get_resource_str(&item.idref) {
out.push_str(&extract_body(&content));
}
}
out.push_str("</body></html>");
fs::write(job.output, out)?;
Ok(())
}
pub fn epub_to_md(job: &Job) -> Result<()> {
let mut doc = epub::doc::EpubDoc::new(job.input)
.map_err(|e| Error::other(format!("epub open: {e}")))?;
let mut md = String::new();
if let Some(title) = doc.mdata("title") {
md.push_str("# ");
md.push_str(&title);
md.push_str("\n\n");
}
let spine = doc.spine.clone();
for item in &spine {
if let Some((content, _)) = doc.get_resource_str(&item.idref) {
let body = extract_body(&content);
md.push_str(&html2md::parse_html(&body));
md.push_str("\n\n");
}
}
fs::write(job.output, md)?;
Ok(())
}
pub fn rtf_to_txt(job: &Job) -> Result<()> {
let raw = fs::read_to_string(job.input)?;
let text = strip_rtf(&raw);
fs::write(job.output, text)?;
Ok(())
}
pub fn rtf_to_md(job: &Job) -> Result<()> {
let raw = fs::read_to_string(job.input)?;
let text = strip_rtf(&raw);
fs::write(job.output, text)?;
Ok(())
}
pub fn pdf_to_txt(job: &Job) -> Result<()> {
let bytes = fs::read(job.input)?;
let text = pdf_extract::extract_text_from_mem(&bytes)
.map_err(|e| Error::other(format!("pdf extract: {e}")))?;
fs::write(job.output, text)?;
Ok(())
}
fn extract_body(html: &str) -> String {
let lower = html.to_ascii_lowercase();
let start = match lower.find("<body") {
Some(i) => i,
None => return html.to_string(),
};
let body_start = match html[start..].find('>') {
Some(rel) => start + rel + 1,
None => return html.to_string(),
};
let end = lower[body_start..]
.find("</body>")
.map(|rel| body_start + rel)
.unwrap_or(html.len());
html[body_start..end].to_string()
}
fn strip_rtf(input: &str) -> String {
let mut out = String::new();
let bytes = input.as_bytes();
let mut i = 0;
while i < bytes.len() {
let b = bytes[i];
match b {
b'{' | b'}' => {
i += 1;
}
b'\\' => {
if i + 1 >= bytes.len() {
break;
}
let next = bytes[i + 1];
if next == b'\\' || next == b'{' || next == b'}' {
out.push(next as char);
i += 2;
} else if next == b'\'' && i + 3 < bytes.len() {
let hex = std::str::from_utf8(&bytes[i + 2..i + 4]).unwrap_or("00");
if let Ok(byte) = u8::from_str_radix(hex, 16) {
out.push(byte as char);
}
i += 4;
} else if next.is_ascii_alphabetic() {
i += 1;
let start = i;
while i < bytes.len() && bytes[i].is_ascii_alphabetic() {
i += 1;
}
let word = &input[start..i];
if i < bytes.len() && bytes[i] == b'-' {
i += 1;
}
while i < bytes.len() && bytes[i].is_ascii_digit() {
i += 1;
}
if i < bytes.len() && bytes[i] == b' ' {
i += 1;
}
match word {
"par" | "line" | "sect" => out.push('\n'),
"tab" => out.push('\t'),
_ => {}
}
} else {
i += 2;
}
}
_ => {
out.push(b as char);
i += 1;
}
}
}
out
}
fn push_html_escaped(out: &mut String, s: &str) {
for c in s.chars() {
match c {
'&' => out.push_str("&"),
'<' => out.push_str("<"),
'>' => out.push_str(">"),
'"' => out.push_str("""),
'\'' => out.push_str("'"),
c => out.push(c),
}
}
}