// Read a Ksudoku's XML save file. use std::collections::HashMap; use std::error; // use std::fmt; use std::fs::File; use std::io::BufReader; use xml::reader::{EventReader, XmlEvent}; use xml::writer::XmlEvent as WrXmlEvent; use xml::EmitterConfig; /* What I want from load_ksudoku: - Puzzle string - Solution string (so I can validate it/check my code) - specific-type & type (combined as a string is ok - I think) - order (as a number) I get values (the puzzle), I also need solution. Having order will allow me to use AnyBoard::new(order) and load it up. */ #[derive(Debug)] pub struct Ksudoku { /// puzzle string: /// _ is blank, starts with 'b' (for 1), 'c' for 2. pub puzzle: String, /// This contains the solution to the puzzle. pub solution: String, /// "Plain" pub specific_type: String, /// "sudoku" pub puzzle_type: String, /// Puzzle width (9, 16, 25). pub order: u8, } impl Default for Ksudoku { fn default() -> Self { Ksudoku { puzzle: String::new(), solution: String::new(), specific_type: String::from("Plain"), puzzle_type: String::from("sudoku"), order: 0, } } } /// Load ksudoku file, returning Ksudoku structure. /// - It is up to the caller to determine if they can handle the types /// and size (order). pub fn load_ksudoku(filename: std::path::PathBuf) -> Result> { let fh = File::open(filename)?; let buffer = BufReader::new(fh); let parser = EventReader::new(buffer); let mut element_name: String = String::new(); let mut details = Ksudoku::default(); for e in parser { match e { Ok(XmlEvent::StartElement { name, attributes, .. }) => { element_name = name.local_name.clone(); if element_name == "graph" { // Check the attributes here // // {"specific-type": "Plain", "type": "sudoku", "order": "9"} let mut attrs: HashMap = HashMap::new(); for a in attributes { attrs.insert(a.name.to_string(), a.value); } let blank = String::new(); details.specific_type = attrs .get(&"specific-type".to_string()) .unwrap_or(&blank) .clone(); details.puzzle_type = attrs.get(&"type".to_string()).unwrap_or(&blank).clone(); let order = attrs.get(&"order".to_string()).unwrap_or(&blank).clone(); if !order.is_empty() { let r = order.parse::(); if r.is_ok() { details.order = r.unwrap(); } else { // Failed to convert order to u8... return Err(Box::new(r.unwrap_err())); } } } } Ok(XmlEvent::Characters(text)) => { if element_name == "values" { details.puzzle = text.clone(); } if element_name == "solution" { details.solution = text.clone(); } } Err(e) => { return Err(Box::new(e)); } _ => {} } } Ok(details) } /// Write out a ksudoku puzzle file pub fn save_ksudoku( filename: std::path::PathBuf, data: &Ksudoku, ) -> Result<(), Box> { let fh = File::create(filename)?; let mut writer = EmitterConfig::new().perform_indent(true).create_writer(fh); let mut event: WrXmlEvent = WrXmlEvent::start_element("ksudoku").into(); writer.write(event)?; event = WrXmlEvent::start_element("game") .attr("had-help", "0") .attr("msecs-elapsed", "0") .into(); writer.write(event)?; event = WrXmlEvent::start_element("puzzle").into(); writer.write(event)?; // Convert order to a string. let order = data.order.to_string(); event = WrXmlEvent::start_element("graph") .attr("specific-type", &data.specific_type) .attr("type", &data.puzzle_type) .attr("order", &order) .into(); writer.write(event)?; event = WrXmlEvent::end_element().into(); writer.write(event)?; // values (puzzle) event = WrXmlEvent::start_element("values").into(); writer.write(event)?; event = WrXmlEvent::characters(&data.puzzle).into(); writer.write(event)?; event = WrXmlEvent::end_element().into(); writer.write(event)?; // solution event = WrXmlEvent::start_element("solution").into(); writer.write(event)?; event = WrXmlEvent::characters(&data.solution).into(); writer.write(event)?; event = WrXmlEvent::end_element().into(); writer.write(event)?; // Apparently, the events are consumed... event = WrXmlEvent::end_element().into(); writer.write(event)?; event = WrXmlEvent::end_element().into(); writer.write(event)?; event = WrXmlEvent::end_element().into(); writer.write(event)?; Ok(()) } #[cfg(test)] mod tests { use crate::ksudoku::*; use crate::sudoku::*; use std::path::PathBuf; fn test_files_path() -> PathBuf { let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR")); d.push("test_files"); d } #[test] fn load_puzzles() { let mut testfile = test_files_path(); testfile.push("missing"); let result = load_ksudoku(testfile); assert!(result.is_err()); let mut testfile = test_files_path(); testfile.push("9-puzzle"); let result = load_ksudoku(testfile); assert!(result.is_ok()); let result = result.unwrap(); assert!(result.puzzle_type == "sudoku"); assert!(result.specific_type == "Plain"); let size = (result.order as f64).sqrt() as u8; let mut board = AnyBoard::new(size); let load = board.load_from_tld('b', '_', &result.puzzle); assert!(load.is_ok()); // Ok, puzzle is loaded! let strings = board.to_strings(); assert_eq!( strings, vec![ " 4 8 ", " 6 1 2 ", "3 9 5 6", " 72 16 ", " 3 9 ", " 18 35 ", "9 3 6 2", " 4 5 8 ", " 3 7 ", ] ); let mut testfile = test_files_path(); testfile.push("16-puzzle"); let result = load_ksudoku(testfile); assert!(result.is_ok()); let result = result.unwrap(); assert!(result.puzzle_type == "sudoku"); assert!(result.specific_type == "Plain"); let size = (result.order as f64).sqrt() as u8; let mut board = AnyBoard::new(size); let load = board.load_from_tld('b', '_', &result.puzzle); assert!(load.is_ok()); // Ok, puzzle is loaded! let strings: Vec = board.to_strings(); assert_eq!( strings, vec![ " D O C IN", "A ENC IL ", "NG AP J MHC ", " P H D A FOL", "IOHKFB A L P", " BN M E L ID ", "LE O AN K BC ", " C H JO EF", "EN GP A F ", " OG J IM L NC", " MK B C N PG ", "C L F PEMIKO", "OPC N H F J ", " EHJ I MA CK", " FM IPA D", "FM L H P " ] ); // board.display(); let mut testfile = test_files_path(); testfile.push("25-puzzle"); let result = load_ksudoku(testfile); assert!(result.is_ok()); let result = result.unwrap(); assert!(result.puzzle_type == "sudoku"); assert!(result.specific_type == "Plain"); let size = (result.order as f64).sqrt() as u8; let mut board = AnyBoard::new(size); let load = board.load_from_tld('b', '_', &result.puzzle); assert!(load.is_ok()); // Ok, puzzle is loaded! // board.display(); let strings: Vec = board.to_strings(); assert_eq!( strings, vec![ " T DPF Y H R WSX L ", "L QF J X C G UB D", " CK S LNRP G UTQO H MA ", "JG H S XELDUPM F B RT", "Y N RQ UCDOK AISJL HP G E", "F OA N UK VQI HY B DT C", " S A C VUE GJQ N I R ", " CPD JGMIA OELSU VBK ", " G LE D BHT XMW K PI Y ", " EXIBOWFLY VAMTJUGQS ", "G HV TMI EWF BR O", " A JFUK W P D M GLXE N ", "R LN G O QI F", " S TCYP B H O X JNAK M ", "M XK ALJ UHQ GP Y", " VTRYBSEFM KWOICJNLG ", " U XD J WCN RTA E QY P ", " HQN COVTJ EBGDL WSF ", " F X Y LWB SVJ R T O ", "E CJ R AN GOF XH V MD B", "S V OI ANHDC TGLQJ YF X P", "CX L K RFUYBWO V A DQ", " FR H DQOC K INBW T UY ", "T JL G I P F OC W", " B KMV Q J H GRI E " ] ); } }