//! Cleans converted markdown. //! //! Conversion from a PDF or an EPUB leaves markdown that is readable but not //! clean: styling tags that survived, control characters a text column will //! reject, headings wrapped in links to their own anchors, and page //! boundaries spelled three different ways. None of it means anything //! downstream, and all of it ends up in an index. //! //! The order of the passes matters. Page boundaries are normalised first, //! because one of the things that marks a page boundary — a form feed — is //! also a control character that the later pass deletes. mod entities; pub mod heading; pub mod html; pub mod pages; pub mod unescape; /// Clean a converted markdown document. /// /// Safe to run twice: the second run changes nothing. pub fn scrub(markdown: &str) -> String { let text = heading::clean_all(markdown); let text = heading::anchors_to_markers(&text); let text = html::strip_tags(&text); let text = html::scrub_control(&text); let text = html::collapse_blank_runs(&text); format!("{}\n", text.trim_matches(textkit::is_space)) } #[cfg(test)] mod tests { use super::*; #[test] fn a_converted_heading_comes_out_readable() { let markdown = "# [I. The Setting](#ch1) {.unnumbered}"; assert_eq!(scrub(markdown), "# I. The Setting\n\n\n"); } #[test] fn running_it_twice_changes_nothing() { let markdown = "\n\n# The Quarrel\n\n\n\nA mill\u{0} \ stood here.
It still does.\n\n\n\nOn."; let once = scrub(markdown); assert_eq!(scrub(&once), once); } #[test] fn a_page_marker_survives_every_pass() { assert!(scrub("\n\ntext").contains("")); } #[test] fn the_result_always_ends_in_exactly_one_newline() { for markdown in ["", "text", "text\n\n\n", " \n"] { let cleaned = scrub(markdown); assert!(cleaned.ends_with('\n'), "{markdown:?}"); assert!(!cleaned.ends_with("\n\n"), "{markdown:?}"); } } #[test] fn page_normalisation_is_not_part_of_cleaning() { // A form feed in ordinary markdown is a control character, not a page // boundary. It is one only to a program that knows it just read a PDF. assert_eq!(scrub("one\u{c}two"), "onetwo\n"); } #[test] fn pagination_first_then_cleaning_is_still_idempotent() { let raw = "one\u{c}two"; let once = scrub(&pages::normalize(raw)); assert!(once.contains(""), "{once:?}"); assert_eq!(scrub(&pages::normalize(&once)), once); } #[test] fn nothing_manufactures_a_page_boundary_on_a_second_run() { // Stripping a tag can leave a separator flush against a newline. If // pagination ran after that, a second pass would find a boundary the // first pass did not. let raw = "{2}------------------------------\nafter\n"; let once = scrub(raw); assert_eq!(scrub(&once), once, "{once:?}"); } }