diff --git a/src/bin/detect_pdf.rs b/src/bin/detect_pdf.rs index 944be50..48913dd 100644 --- a/src/bin/detect_pdf.rs +++ b/src/bin/detect_pdf.rs @@ -5,9 +5,31 @@ use pdf_inspector::{ ProcessMode, }; use std::env; +use std::fmt::Write; use std::process; use std::time::Instant; +/// Escape a string for embedding in a JSON string value. +fn json_escape(s: &str) -> String { + let mut out = String::with_capacity(s.len() + 16); + for ch in s.chars() { + match ch { + '\\' => out.push_str("\\\\"), + '"' => out.push_str("\\\""), + '\n' => out.push_str("\\n"), + '\r' => out.push_str("\\r"), + '\t' => out.push_str("\\t"), + '\x08' => out.push_str("\\b"), + '\x0C' => out.push_str("\\f"), + c if c < '\x20' => { + let _ = write!(out, "\\u{:04x}", c as u32); + } + c => out.push(c), + } + } + out +} + fn main() { env_logger::init(); let args: Vec = env::args().collect(); @@ -152,7 +174,7 @@ fn run_detect_only(pdf_path: &str, json_output: bool, start: Instant) { result .title .as_ref() - .map(|t| format!("\"{}\"", t.replace('"', "\\\""))) + .map(|t| format!("\"{}\"", json_escape(t))) .unwrap_or_else(|| "null".to_string()), result.ocr_recommended, ocr_pages.join(","), diff --git a/src/bin/pdf2md.rs b/src/bin/pdf2md.rs index c925ebe..d0b2f9e 100644 --- a/src/bin/pdf2md.rs +++ b/src/bin/pdf2md.rs @@ -6,9 +6,34 @@ use pdf_inspector::{ }; use std::collections::HashSet; use std::env; +use std::fmt::Write; use std::fs; use std::process; +/// Escape a string for embedding in a JSON string value. +/// +/// Handles all characters that the JSON spec requires to be escaped: +/// backslash, double-quote, and control characters U+0000..U+001F. +fn json_escape(s: &str) -> String { + let mut out = String::with_capacity(s.len() + 16); + for ch in s.chars() { + match ch { + '\\' => out.push_str("\\\\"), + '"' => out.push_str("\\\""), + '\n' => out.push_str("\\n"), + '\r' => out.push_str("\\r"), + '\t' => out.push_str("\\t"), + '\x08' => out.push_str("\\b"), + '\x0C' => out.push_str("\\f"), + c if c < '\x20' => { + let _ = write!(out, "\\u{:04x}", c as u32); + } + c => out.push(c), + } + } + out +} + /// Parse a page specification like "1,3,5-10,20" into a HashSet of page numbers. fn parse_page_spec(spec: &str) -> Result, String> { let mut pages = HashSet::new(); @@ -185,11 +210,7 @@ fn main() { let md_escaped = result .markdown .as_ref() - .map(|m| { - m.replace('\\', "\\\\") - .replace('"', "\\\"") - .replace('\n', "\\n") - }) + .map(|m| json_escape(m)) .unwrap_or_default(); let ocr_pages: Vec = result