diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 7ca3c030..2ed9d0e7 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -15,6 +15,7 @@ jobs: steps: - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@nightly - name: Install Rust run: rustup component add rustfmt clippy @@ -22,5 +23,5 @@ jobs: - name: Run Lint & Tests run: | cargo clippy -- -D warnings - cargo fmt --all -- --check + cargo +nightly fmt --all cargo test diff --git a/Cargo.toml b/Cargo.toml index 3c9270d1..3453f9eb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,42 +1,46 @@ [package] name = "umya-spreadsheet" -version = "2.2.0" +version = "3.0.0" authors = ["MathNya "] repository = "https://github.com/MathNya/umya-spreadsheet" keywords = ["excel", "spreadsheet", "xlsx", "reader", "writer"] +categories = ["parser-implementations", "encoding", "text-processing"] license = "MIT" readme = "README.md" description = "umya-spreadsheet is a library written in pure Rust to read and write xlsx file." edition = "2021" +rust-version = "1.79.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] aes = "0.8.4" -ahash = "0.8.11" base64 = "0.22.1" byteorder = "1.5" cbc = "0.1.2" cfb = "0.10.0" -chrono = { version = "0.4.38", default-features = false, features = ["clock"] } +chrono = { version = "0.4.39", default-features = false, features = ["clock"] } encoding_rs = "0.8.35" fancy-regex = "0.14.0" -getrandom = { version = "0.2.15" } hmac = "0.12.1" html_parser = "0.7.0" -image = { version = "0.25.5", optional = true } -lazy_static = "1.5.0" +imagesize = "0.14" md-5 = "0.10.6" -regex = "1.11.1" +num-traits = "0.2.19" +paste = "1.0.15" +phf = { version = "0.11.2", features = ["macros"] } +quick-xml = { version = "0.37.1", features = ["serialize"] } +rand = "0.8.5" +rgb = "0.8.50" sha2 = "0.10.8" -thin-vec = "0.2.13" thousands = "0.2.0" -quick-xml = { version = "0.37.1", features = ["serialize"] } -zip = { version = "2.2.1", default-features = false, features = ["deflate"] } +zip = { version = "2.2.2", default-features = false, features = ["deflate"] } + +[dev-dependencies] +hex-literal = "0.4.1" [lib] doctest = false [features] -js = ["getrandom/js"] -default = ["image"] + diff --git a/README.md b/README.md index 13871177..bdfa03ef 100644 --- a/README.md +++ b/README.md @@ -25,14 +25,14 @@ Please be aware of this. Copies the style of the specified column or row. ```rust let mut book = umya_spreadsheet::reader::xlsx::read(path).unwrap(); -let sheet = book.get_sheet_mut(&0).unwrap(); +let sheet = book.get_sheet_mut(0).unwrap(); sheet.copy_row_styling(&3, &5, None, None); sheet.copy_col_styling(&3, &5, None, None); ``` #### * The function to create a new comment has been implemented. ```rust let mut book = umya_spreadsheet::reader::xlsx::read(path).unwrap(); -let sheet = book.get_sheet_mut(&0).unwrap(); +let sheet = book.get_sheet_mut(0).unwrap(); let mut comment = Comment::default(); comment.new_comment("B2"); comment.set_text_string("TEST"); @@ -77,25 +77,25 @@ let mut book = umya_spreadsheet::new_file(); ### Write file ```rust let path = std::path::Path::new("./tests/result_files/bbb.xlsx"); -let _ = umya_spreadsheet::writer::xlsx::write(&book, path); +let _unused = umya_spreadsheet::writer::xlsx::write(&book, path); ``` ### Write file with password ```rust let path = std::path::Path::new("./tests/result_files/bbb.xlsx"); -let _ = umya_spreadsheet::writer::xlsx::write_with_password(&book, path, "password"); +let _unused = umya_spreadsheet::writer::xlsx::write_with_password(&book, path, "password"); ``` ```rust let from_path = std::path::Path::new("./tests/test_files/aaa.xlsx"); let to_path = std::path::Path::new("./tests/result_files/bbb.xlsx"); -let _ = umya_spreadsheet::writer::xlsx::set_password(&from_path, &to_path, "password"); +let _unused = umya_spreadsheet::writer::xlsx::set_password(&from_path, &to_path, "password"); ``` ### Read Value ```rust let mut book = umya_spreadsheet::new_file(); book.get_sheet_by_name("Sheet1").unwrap().get_cell("A1").get_value(); book.get_sheet_by_name("Sheet1").unwrap().get_cell((1, 1)).get_value(); -book.get_sheet_by_name("Sheet1").unwrap().get_cell((&1, &1)).get_value(); -book.get_sheet_mut(0).unwrap().get_cell((&1, &1)).get_value(); +book.get_sheet_by_name("Sheet1").unwrap().get_cell((1, 1)).get_value(); +book.get_sheet_mut(0).unwrap().get_cell((1, 1)).get_value(); ``` ### Change Value ```rust @@ -142,15 +142,15 @@ book.get_sheet_by_name_mut("Sheet1").unwrap() ### Struct -Pass the book as a ```Spreadsheet``` to modify it in other functions. +Pass the book as a ```Workbook``` to modify it in other functions. ```rust let mut book = umya_spreadsheet::new_file(); -let _ = book.new_sheet("Sheet2"); +let _unused = book.new_sheet("Sheet2"); update_excel(&mut book); -fn update_excel(book: &mut Spreadsheet) { +fn update_excel(book: &mut Workbook) { book.get_sheet_by_name_mut("Sheet2").unwrap().get_cell_mut("A1").set_value("Test"); } ``` @@ -184,3 +184,7 @@ Contributions by way of pull requests are welcome! Please make sure your code u * `cargo fmt` for formatting * [clippy](https://github.com/rust-lang/rust-clippy) +```rust +cargo +nightly fmt --all +cargo clippy -- -D warnings +``` diff --git a/clippy.toml b/clippy.toml new file mode 100644 index 00000000..c024f7ef --- /dev/null +++ b/clippy.toml @@ -0,0 +1 @@ +allowed-duplicate-crates = [ "thiserror", "thiserror-impl", "miniz_oxide", "syn"] \ No newline at end of file diff --git a/justfile b/justfile new file mode 100644 index 00000000..1b0ee7b0 --- /dev/null +++ b/justfile @@ -0,0 +1,23 @@ +# Justfile + +fmt: + cargo +nightly fmt --all + +clippy: + cargo +nightly clippy + +clean: + cargo clean + +gitclean: + git clean -dfx + +test: + #!/usr/bin/env sh + if cargo nextest --help &> /dev/null; then + # If successful, run 'cargo nextest run' + cargo nextest run + else + # If not successful, run 'cargo test' + cargo test + fi \ No newline at end of file diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 00000000..9c38971f --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,58 @@ +# Edition and Feature Flags +style_edition = "2024" +unstable_features = true + +# Brace and Parentheses Style +brace_style = "SameLineWhere" +remove_nested_parens = true + +# Layout and Line Management +condense_wildcard_suffixes = true +error_on_line_overflow = true +fn_params_layout = "Tall" +overflow_delimited_expr = false +type_punctuation_density = "Wide" +where_single_line = false + +# Comment and Documentation Formatting +normalize_comments = true +normalize_doc_attributes = true +wrap_comments = true +format_code_in_doc_comments = true + +# Attribute and Shorthand Usage +inline_attribute_width = 0 +use_field_init_shorthand = true +use_try_shorthand = true +use_small_heuristics = "Default" + +# Match and Block Formatting +match_arm_blocks = true +match_arm_leading_pipes = "Never" +match_block_trailing_comma = false + +# Code Structure and Reordering +reorder_impl_items = true +reorder_modules = true + +# String and Macro Formatting +format_macro_matchers = true +format_strings = true + +# Indentation Style +indent_style = "Block" + +# Imports Configuration +group_imports = "StdExternalCrate" +imports_granularity = "Crate" +imports_layout = "Vertical" +reorder_imports = true + +# Module and Struct Configuration +struct_field_align_threshold = 50 + +# Ignored Files +ignore = [ + "src/helper/const_str.rs", + "src/helper/crypt/constants.rs", +] \ No newline at end of file diff --git a/src/helper/address.rs b/src/helper/address.rs index 69de88d5..29f59646 100644 --- a/src/helper/address.rs +++ b/src/helper/address.rs @@ -1,17 +1,59 @@ -use fancy_regex::Regex; +use crate::helper::utils::compile_regex; +#[must_use] pub fn split_address(address: &str) -> (&str, &str) { address .rsplit_once('!') - .map(|(sheet_name, range)| (sheet_name.trim_matches(&['\'', '"'][..]), range)) - .unwrap_or(("", address)) + .map_or(("", address), |(sheet_name, range)| { + (sheet_name.trim_matches(&['\'', '"'][..]), range) + }) } +#[must_use] pub fn join_address(sheet_name: &str, address: &str) -> String { - if sheet_name == "" { + if sheet_name.is_empty() { return address.to_string(); } - format!("{}!{}", sheet_name, address) + format!("{sheet_name}!{address}") +} + +/// Checks if the given input string is a valid address format. +/// +/// The address format is defined by the following regular expression: +/// `^([^\:\\\?$$$$\/\*]+\!)?(\$?[A-Z]{1,3}\$?[0-9]+)(\:\$?[A-Z]{1,3}\$?[0-9]+)? +/// $`. +/// +/// # Parameters +/// +/// - `input`: A string slice that can be converted to a string reference. This +/// is the input string to be checked against the address format. +/// +/// # Returns +/// +/// Returns `true` if the input string matches the address format, and `false` +/// otherwise. +/// +/// # Panics +/// +/// This function may panic if the regular expression fails to compile. However, +/// since the regular expression is initialized only once and is hardcoded, this +/// is unlikely to occur unless there is a bug in the regex itself. The panic +/// will occur during the first call to this function if the regex is invalid. +/// +/// # Examples +/// +/// ``` +/// let valid_address = "$A1"; +/// assert!(is_address(valid_address)); +/// +/// let invalid_address = "invalid_address"; +/// assert!(!is_address(invalid_address)); +/// ``` +pub fn is_address>(input: S) -> bool { + let regex = compile_regex!( + r"^([^\:\\\?\[\]\/\*]+\!)?(\$?[A-Z]{1,3}\$?[0-9]+)(\:\$?[A-Z]{1,3}\$?[0-9]+)?$" + ); + regex.is_match(input.as_ref()).unwrap() } #[test] @@ -23,13 +65,6 @@ fn split_address_test() { assert_eq!(split_address(r#"'she"et1'!A1:B2"#), (r#"she"et1"#, "A1:B2")); } -pub fn is_address>(input: S) -> bool { - let re = - Regex::new(r"^([^\:\\\?\[\]\/\*]+\!)?(\$?[A-Z]{1,3}\$?[0-9]+)(\:\$?[A-Z]{1,3}\$?[0-9]+)?$") - .unwrap(); - re.is_match(input.as_ref()).unwrap() -} - #[test] fn is_address_test() { assert!(is_address("A1")); diff --git a/src/helper/binary.rs b/src/helper/binary.rs index 2d11c908..2c68811f 100644 --- a/src/helper/binary.rs +++ b/src/helper/binary.rs @@ -1,27 +1,47 @@ -use crate::structs::MediaObject; -use base64::{engine::general_purpose::STANDARD, Engine as _}; -use std::fs; -use std::fs::File; -use std::io::BufReader; -use std::io::Cursor; -use std::io::Read; +use std::{ + fs, + path::Path, +}; -#[inline] -pub fn get_binary_data(path: &str) -> Vec { - let path = std::path::Path::new(path); - let mut buf = Vec::new(); +use crate::structs::MediaObject; - let file = File::open(path).unwrap(); - BufReader::new(file).read_to_end(&mut buf).unwrap(); - return buf; -} +/// Creates a `MediaObject` from the file at the specified path. +/// +/// # Parameters +/// +/// - `path`: A reference to a path from which to create the `MediaObject`. +/// +/// # Returns +/// +/// Returns a `MediaObject` populated with the image data, name, and title +/// extracted from the file at the specified path. +/// +/// # Panics +/// +/// This function will panic if the file cannot be read, as it calls `unwrap()` +/// on the result of `get_binary_data(path)`. Ensure that the file exists and is +/// readable before calling this function. +/// +/// # Example +/// +/// ``` +/// let media_object = make_media_object("path/to/image.png"); +/// ``` +#[must_use] +pub fn make_media_object>(path: P) -> MediaObject { + let path = path.as_ref(); + let file_name = path + .file_name() + .and_then(|name| name.to_str()) + .unwrap_or(""); + let title = path + .file_stem() + .and_then(|stem| stem.to_str()) + .unwrap_or(""); -#[inline] -pub fn make_media_object(path: &str) -> MediaObject { - let name = path.split("/").last().unwrap(); let mut obj = MediaObject::default(); - obj.set_image_data(get_binary_data(path)); - obj.set_image_name(name); - obj.set_image_title(name.split(".").next().unwrap_or("")); + obj.set_image_data(fs::read(path).unwrap()); + obj.set_image_name(file_name); + obj.set_image_title(title); obj } diff --git a/src/helper/color.rs b/src/helper/color.rs index eb670fa0..ff4e0aea 100644 --- a/src/helper/color.rs +++ b/src/helper/color.rs @@ -1,6 +1,4 @@ -/** - * https://ciintelligence.blogspot.com/2012/02/converting-excel-theme-color-and-tint.html - */ +/// #[derive(Default, Debug, Clone, PartialEq, PartialOrd)] pub struct HlsColor { @@ -19,57 +17,58 @@ pub struct MsHlsColor { const RGBMAX: f64 = 255.0; const HLSMAX: f64 = 255.0; -pub fn calc_tint(rgb: &str, tint: &f64) -> String { +#[must_use] +pub fn calc_tint(rgb: &str, tint: f64) -> String { let mut ms_hls = convert_rgb_to_ms_hls(rgb); - ms_hls.l = calculate_final_lum_value(tint, &(ms_hls.l as f64)); + ms_hls.l = calculate_final_lum_value(tint, f64::from(ms_hls.l)); convert_ms_hls_to_rgb(&ms_hls) } -pub fn calculate_final_lum_value(tint: &f64, lum: &f64) -> i32 { - let mut lum1 = 0.0; - - if tint < &0.0 { - lum1 = lum * (1.0 + tint); +#[must_use] +pub fn calculate_final_lum_value(tint: f64, lum: f64) -> i32 { + let lum1 = if tint < 0.0 { + lum * (1.0 + tint) } else { - lum1 = lum * (1.0 - tint) + (HLSMAX - HLSMAX * (1.0 - tint)); - } + lum * (1.0 - tint) + (HLSMAX - HLSMAX * (1.0 - tint)) + }; - return to_i32(lum1); + to_i32(lum1) } +#[must_use] pub fn split_rgb(rgb: &str) -> (i32, i32, i32) { - let r_str = rgb.chars().skip(0).take(2).collect::(); - let g_str = rgb.chars().skip(2).take(2).collect::(); - let b_str = rgb.chars().skip(4).take(2).collect::(); - let r = i32::from_str_radix(&r_str, 16).unwrap(); - let g = i32::from_str_radix(&g_str, 16).unwrap(); - let b = i32::from_str_radix(&b_str, 16).unwrap(); + let r = i32::from_str_radix(&rgb[0..2], 16).unwrap(); + let g = i32::from_str_radix(&rgb[2..4], 16).unwrap(); + let b = i32::from_str_radix(&rgb[4..6], 16).unwrap(); (r, g, b) } #[inline] -pub fn join_rgb(r: &i32, g: &i32, b: &i32) -> String { - format!("{:02X}{:02X}{:02X}", r, g, b) +#[must_use] +pub fn join_rgb(r: i32, g: i32, b: i32) -> String { + format!("{r:02X}{g:02X}{b:02X}") } +#[must_use] pub fn convert_rgb_to_ms_hls(rgb: &str) -> MsHlsColor { let hls = convert_rgb_to_hls(rgb); - - let mut ms_hls = MsHlsColor::default(); - ms_hls.h = to_i32(hls.h * self::HLSMAX); - ms_hls.l = to_i32(hls.l * self::HLSMAX); - ms_hls.s = to_i32(hls.s * self::HLSMAX); - ms_hls + MsHlsColor { + h: to_i32(hls.h * HLSMAX), + l: to_i32(hls.l * HLSMAX), + s: to_i32(hls.s * HLSMAX), + } } +#[must_use] +#[allow(clippy::float_cmp)] pub fn convert_rgb_to_hls(rgb: &str) -> HlsColor { let mut hls = HlsColor::default(); let (r_i, g_i, b_i) = split_rgb(rgb); - let r = r_i as f64 / RGBMAX; - let g = g_i as f64 / RGBMAX; - let b = b_i as f64 / RGBMAX; + let r = f64::from(r_i) / RGBMAX; + let g = f64::from(g_i) / RGBMAX; + let b = f64::from(b_i) / RGBMAX; let mut min = r; if min > g { @@ -116,23 +115,26 @@ pub fn convert_rgb_to_hls(rgb: &str) -> HlsColor { hls.h = 4.0 + gc - rc; } - hls.h = positive_decimal_part(&(hls.h / 6.0)); + hls.h = positive_decimal_part(hls.h / 6.0); - return hls; + hls } +#[must_use] pub fn convert_ms_hls_to_rgb(ms_hls: &MsHlsColor) -> String { - let mut hls = HlsColor::default(); - hls.h = ms_hls.h as f64 / self::HLSMAX; - hls.l = ms_hls.l as f64 / self::HLSMAX; - hls.s = ms_hls.s as f64 / self::HLSMAX; + let hls = HlsColor { + h: (f64::from(ms_hls.h) / HLSMAX), + l: (f64::from(ms_hls.l) / HLSMAX), + s: (f64::from(ms_hls.s) / HLSMAX), + }; convert_hls_to_rgb(&hls) } +#[must_use] pub fn convert_hls_to_rgb(hls: &HlsColor) -> String { if hls.s == 0.0 { let rtn_l = to_i32(hls.l * RGBMAX); - return join_rgb(&rtn_l, &rtn_l, &rtn_l); + return join_rgb(rtn_l, rtn_l, rtn_l); } let t1 = if hls.l < 0.5 { @@ -144,39 +146,35 @@ pub fn convert_hls_to_rgb(hls: &HlsColor) -> String { let t2 = 2.0 * hls.l - t1; let h = hls.h; let t_r = h + (1.0 / 3.0); - let r = set_color(&t1, &t2, &t_r); + let r = set_color(t1, t2, t_r); let t_g = h; - let g = set_color(&t1, &t2, &t_g); + let g = set_color(t1, t2, t_g); let t_b = h - (1.0 / 3.0); - let b = set_color(&t1, &t2, &t_b); + let b = set_color(t1, t2, t_b); let rtn_r = to_i32(r * RGBMAX); let rtn_g = to_i32(g * RGBMAX); let rtn_b = to_i32(b * RGBMAX); - join_rgb(&rtn_r, &rtn_g, &rtn_b) + join_rgb(rtn_r, rtn_g, rtn_b) } -pub fn set_color(t1: &f64, t2: &f64, t3: &f64) -> f64 { - let mut t1 = t1.clone(); - let mut t2 = t2.clone(); - let mut t3 = positive_decimal_part(t3); - - let mut color: f64 = 0.0; +#[must_use] +pub fn set_color(t1: f64, t2: f64, t3: f64) -> f64 { + let t3 = positive_decimal_part(t3); if 6.0 * t3 < 1.0 { - color = t2 + (t1 - t2) * 6.0 * t3; + t2 + (t1 - t2) * 6.0 * t3 } else if 2.0 * t3 < 1.0 { - color = t1; + t1 } else if 3.0 * t3 < 2.0 { - color = t2 + (t1 - t2) * ((2.0 / 3.0) - t3) * 6.0; + t2 + (t1 - t2) * ((2.0 / 3.0) - t3) * 6.0 } else { - color = t2; + t2 } - color } #[inline] -fn positive_decimal_part(hue: &f64) -> f64 { +fn positive_decimal_part(hue: f64) -> f64 { let hue = hue % 1.0; if hue >= 0.0 { @@ -187,5 +185,5 @@ fn positive_decimal_part(hue: &f64) -> f64 { #[inline] fn to_i32(num: f64) -> i32 { - num.round() as i32 + num_traits::cast(num.round()).unwrap() } diff --git a/src/helper/const_str.rs b/src/helper/const_str.rs index dee8d2eb..3e0f4d2a 100644 --- a/src/helper/const_str.rs +++ b/src/helper/const_str.rs @@ -1,140 +1,111 @@ -pub(crate) const CERTIFICATE_NS: &str = - "http://schemas.microsoft.com/office/2006/keyEncryptor/certificate"; -pub(crate) const CHART_NS: &str = - "http://schemas.openxmlformats.org/officeDocument/2006/relationships/chart"; -pub(crate) const COMMENTS_NS: &str = - "http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments"; -pub(crate) const CONTYPES_NS: &str = "http://schemas.openxmlformats.org/package/2006/content-types"; -pub(crate) const COREPROPS_NS: &str = - "http://schemas.openxmlformats.org/package/2006/metadata/core-properties"; -pub(crate) const CUSTOMUI_NS: &str = - "http://schemas.microsoft.com/office/2006/relationships/ui/extensibility"; -pub(crate) const DCMITYPE_NS: &str = "http://purl.org/dc/dcmitype/"; -pub(crate) const DCORE_NS: &str = "http://purl.org/dc/elements/1.1/"; -pub(crate) const DCTERMS_NS: &str = "http://purl.org/dc/terms/"; -pub(crate) const EXCEL_MAIN_NS: &str = "http://schemas.microsoft.com/office/excel/2006/main"; -pub(crate) const DRAWING_CHART_NS: &str = - "http://schemas.microsoft.com/office/drawing/2007/8/2/chart"; -pub(crate) const DRAWING_MAIN_NS: &str = "http://schemas.microsoft.com/office/drawing/2010/main"; -pub(crate) const DRAWINGML_CHART_NS: &str = - "http://schemas.openxmlformats.org/drawingml/2006/chart"; -pub(crate) const DRAWINGML_MAIN_NS: &str = "http://schemas.openxmlformats.org/drawingml/2006/main"; -pub(crate) const DRAWINGS_NS: &str = - "http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing"; -pub(crate) const ENCRYPTION_NS: &str = "http://schemas.microsoft.com/office/2006/encryption"; -pub(crate) const EXCEL_NS: &str = "urn:schemas-microsoft-com:office:excel"; -pub(crate) const HYPERLINK_NS: &str = - "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink"; -pub(crate) const IMAGE_NS: &str = - "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image"; -pub(crate) const MC_NS: &str = "http://schemas.openxmlformats.org/markup-compatibility/2006"; -pub(crate) const OFCDOC_NS: &str = - "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument"; -pub(crate) const OFFICE_NS: &str = "urn:schemas-microsoft-com:office:office"; -pub(crate) const OLE_OBJECT_NS: &str = - "http://schemas.openxmlformats.org/officeDocument/2006/relationships/oleObject"; -pub(crate) const PASSWORD_NS: &str = - "http://schemas.microsoft.com/office/2006/keyEncryptor/password"; -pub(crate) const PACKAGE_NS: &str = - "http://schemas.openxmlformats.org/officeDocument/2006/relationships/package"; -pub(crate) const REL_NS: &str = "http://schemas.openxmlformats.org/package/2006/relationships"; -pub(crate) const REL_OFC_NS: &str = - "http://schemas.openxmlformats.org/officeDocument/2006/relationships"; -pub(crate) const PRINTER_SETTINGS_NS: &str = - "http://schemas.openxmlformats.org/officeDocument/2006/relationships/printerSettings"; -pub(crate) const PIVOT_CACHE_DEF_NS: &str = - "http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotCacheDefinition"; -pub(crate) const STYLES_NS: &str = - "http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles"; -pub(crate) const SHARED_STRINGS_NS: &str = - "http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings"; -pub(crate) const SHEET_DRAWING_NS: &str = - "http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing"; -pub(crate) const SHEET_MAIN_NS: &str = "http://schemas.openxmlformats.org/spreadsheetml/2006/main"; -pub(crate) const SHEET_MS_MAIN_NS: &str = - "http://schemas.microsoft.com/office/spreadsheetml/2009/9/main"; -pub(crate) const SHEET_MS_REVISION_NS: &str = - "http://schemas.microsoft.com/office/spreadsheetml/2014/revision"; -pub(crate) const SHEETML_AC_NS: &str = - "http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac"; -pub(crate) const TABLE_NS: &str = - "http://schemas.openxmlformats.org/officeDocument/2006/relationships/table"; -pub(crate) const THEME_NS: &str = - "http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme"; -pub(crate) const VBA_PROJECT_NS: &str = - "http://schemas.microsoft.com/office/2006/relationships/vbaProject"; -pub(crate) const VML_NS: &str = "urn:schemas-microsoft-com:vml"; -pub(crate) const VML_DRAWING_NS: &str = - "http://schemas.openxmlformats.org/officeDocument/2006/relationships/vmlDrawing"; -pub(crate) const VTYPES_NS: &str = - "http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes"; -pub(crate) const WORKSHEET_NS: &str = - "http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet"; -pub(crate) const XPROPS_NS: &str = - "http://schemas.openxmlformats.org/officeDocument/2006/extended-properties"; -pub(crate) const XSI_NS: &str = "http://www.w3.org/2001/XMLSchema-instance"; -pub(crate) const CUSTOM_PROPS_NS: &str = - "http://schemas.openxmlformats.org/officeDocument/2006/custom-properties"; +macro_rules! declare_const_strings { + ($($name:ident => $value:expr),*) => { + $( + pub(crate) const $name: &str = $value; + )* + }; +} -pub(crate) const XPROPS_REL: &str = - "http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties"; -pub(crate) const CUSTOM_PROPS_REL: &str = - "http://schemas.openxmlformats.org/officeDocument/2006/relationships/custom-properties"; -pub(crate) const COREPROPS_REL: &str = - "http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties"; +declare_const_strings! { + CERTIFICATE_NS => "http://schemas.microsoft.com/office/2006/keyEncryptor/certificate", + CHART_NS => "http://schemas.openxmlformats.org/officeDocument/2006/relationships/chart", + CHART_TYPE => "application/vnd.openxmlformats-officedocument.drawingml.chart+xml", + COMMENTS_NS => "http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments", + COMMENTS_TYPE => "application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml", + THREADED_COMMENT_NS => "http://schemas.microsoft.com/office/2017/10/relationships/threadedComment", + THREADED_COMMENTS_NS => "http://schemas.microsoft.com/office/spreadsheetml/2018/threadedcomments", + PERSION_NS => "http://schemas.microsoft.com/office/2017/10/relationships/person", + PKG_PERSON => "xl/persons/person.xml", + CONTYPES_NS => "http://schemas.openxmlformats.org/package/2006/content-types", + CORE_PROPS_TYPE => "application/vnd.openxmlformats-package.core-properties+xml", + COREPROPS_NS => "http://schemas.openxmlformats.org/package/2006/metadata/core-properties", + COREPROPS_REL => "http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties", + CUSTOM_PROPS_NS => "http://schemas.openxmlformats.org/officeDocument/2006/custom-properties", + CUSTOM_PROPS_REL => "http://schemas.openxmlformats.org/officeDocument/2006/relationships/custom-properties", + CUSTOM_PROPS_TYPE => "application/vnd.openxmlformats-officedocument.custom-properties+xml", + CUSTOMUI_NS => "http://schemas.microsoft.com/office/2006/relationships/ui/extensibility", + DCMITYPE_NS => "http://purl.org/dc/dcmitype/", + DCORE_NS => "http://purl.org/dc/elements/1.1/", + DCTERMS_NS => "http://purl.org/dc/terms/", + DRAWING_CHART_NS => "http://schemas.microsoft.com/office/drawing/2007/8/2/chart", + DRAWING_CHART_2015_NS => "http://schemas.microsoft.com/office/drawing/2015/06/chart", + DRAWING_MAIN_NS => "http://schemas.microsoft.com/office/drawing/2010/main", + DRAWING_TYPE => "application/vnd.openxmlformats-officedocument.drawing+xml", + DRAWINGML_CHART_NS => "http://schemas.openxmlformats.org/drawingml/2006/chart", + DRAWINGML_MAIN_NS => "http://schemas.openxmlformats.org/drawingml/2006/main", + DRAWINGS_NS => "http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing", + ENCRYPTION_NS => "http://schemas.microsoft.com/office/2006/encryption", + EXCEL_MAIN_NS => "http://schemas.microsoft.com/office/excel/2006/main", + EXCEL_NS => "urn:schemas-microsoft-com:office:excel", + HYPERLINK_NS => "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink", + IMAGE_NS => "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image", + MC_NS => "http://schemas.openxmlformats.org/markup-compatibility/2006", + OFCDOC_NS => "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument", + OFFICE_NS => "urn:schemas-microsoft-com:office:office", + OLE_OBJECT_NS => "http://schemas.openxmlformats.org/officeDocument/2006/relationships/oleObject", + OLE_OBJECT_TYPE => "application/vnd.openxmlformats-officedocument.oleObject", + PACKAGE_NS => "http://schemas.openxmlformats.org/officeDocument/2006/relationships/package", + PASSWORD_NS => "http://schemas.microsoft.com/office/2006/keyEncryptor/password", + PIVOT_CACHE_DEF_NS => "http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotCacheDefinition", + PRINTER_SETTINGS_NS => "http://schemas.openxmlformats.org/officeDocument/2006/relationships/printerSettings", + PRNTR_SETTINGS_TYPE => "application/vnd.openxmlformats-officedocument.spreadsheetml.printerSettings", + REL_NS => "http://schemas.openxmlformats.org/package/2006/relationships", + REL_OFC_NS => "http://schemas.openxmlformats.org/officeDocument/2006/relationships", + REL_TYPE => "application/vnd.openxmlformats-package.relationships+xml", + SHARED_STRINGS_NS => "http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings", + SHARED_STRINGS_TYPE => "application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml", + SHEET_DRAWING_NS => "http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing", + SHEET_MAIN_NS => "http://schemas.openxmlformats.org/spreadsheetml/2006/main", + SHEET_MS_MAIN_NS => "http://schemas.microsoft.com/office/spreadsheetml/2009/9/main", + SHEET_MS_REVISION_NS => "http://schemas.microsoft.com/office/spreadsheetml/2014/revision", + SHEET_TYPE => "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml", + SHEETML_AC_NS => "http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac", + STYLES_NS => "http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles", + STYLES_TYPE => "application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml", + TABLE_NS => "http://schemas.openxmlformats.org/officeDocument/2006/relationships/table", + PIVOT_TABLE_NS => "http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotTable", + TABLE_TYPE => "application/vnd.openxmlformats-officedocument.spreadsheetml.table+xml", + PIVOT_TABLE_TYPE => "application/vnd.openxmlformats-officedocument.spreadsheetml.pivotTable+xml", + PIVOT_CACHE_DEF_TYPE => "application/vnd.openxmlformats-officedocument.spreadsheetml.pivotCacheDefinition+xml", + THEME_NS => "http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme", + THEME_TYPE => "application/vnd.openxmlformats-officedocument.theme+xml", + VBA_PROJECT_NS => "http://schemas.microsoft.com/office/2006/relationships/vbaProject", + JSA_PROJECT_NS => "http://schemas.onlyoffice.com/jsaProject", + VBA_TYPE => "application/vnd.ms-office.vbaProject", + VML_DRAWING_NS => "http://schemas.openxmlformats.org/officeDocument/2006/relationships/vmlDrawing", + VML_DRAWING_TYPE => "application/vnd.openxmlformats-officedocument.vmlDrawing", + VML_NS => "urn:schemas-microsoft-com:vml", + VTYPES_NS => "http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes", + WORKBOOK => "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + WORKBOOK_MACRO_TYPE => "application/vnd.ms-excel.sheet.macroEnabled.main+xml", + WORKBOOK_TYPE => "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml", + WORKSHEET_NS => "http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet", + XPROPS_NS => "http://schemas.openxmlformats.org/officeDocument/2006/extended-properties", + XPROPS_REL => "http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties", + XPROPS_TYPE => "application/vnd.openxmlformats-officedocument.extended-properties+xml", + XSI_NS => "http://www.w3.org/2001/XMLSchema-instance", + PKG_CHARTS => "xl/charts", + PKG_DRAWINGS => "xl/drawings", + PKG_DRAWINGS_RELS => "xl/drawings/_rels/drawing", + PKG_EMBEDDINGS => "xl/embeddings", + PKG_MEDIA => "xl/media", + PKG_PRNTR_SETTINGS => "xl/printerSettings", + PKG_SHARED_STRINGS => "xl/sharedStrings.xml", + PKG_SHEET => "xl/worksheets/sheet", + PKG_SHEET_RELS => "xl/worksheets/_rels/sheet", + PKG_STYLES => "xl/styles.xml", + PKG_TABLES => "xl/tables", + PKG_THEME => "xl/theme/theme1.xml", + PKG_VBA_PROJECT => "xl/vbaProject.bin", + PKG_JSA_PROJECT => "xl/jsaProject.bin", + PKG_VML_DRAWING_RELS => "xl/drawings/_rels/vmlDrawing", + PKG_WORKBOOK => "xl/workbook.xml", + PKG_WORKBOOK_RELS => "xl/_rels/workbook.xml.rels", -pub(crate) const CHART_TYPE: &str = - "application/vnd.openxmlformats-officedocument.drawingml.chart+xml"; -pub(crate) const COMMENTS_TYPE: &str = - "application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml"; -pub(crate) const CORE_PROPS_TYPE: &str = - "application/vnd.openxmlformats-package.core-properties+xml"; -pub(crate) const DRAWING_TYPE: &str = "application/vnd.openxmlformats-officedocument.drawing+xml"; -pub(crate) const OLE_OBJECT_TYPE: &str = "application/vnd.openxmlformats-officedocument.oleObject"; -pub(crate) const PRNTR_SETTINGS_TYPE: &str = - "application/vnd.openxmlformats-officedocument.spreadsheetml.printerSettings"; -pub(crate) const REL_TYPE: &str = "application/vnd.openxmlformats-package.relationships+xml"; -pub(crate) const SHARED_STRINGS_TYPE: &str = - "application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml"; -pub(crate) const SHEET_TYPE: &str = - "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"; -pub(crate) const STYLES_TYPE: &str = - "application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"; -pub(crate) const THEME_TYPE: &str = "application/vnd.openxmlformats-officedocument.theme+xml"; -pub(crate) const TABLE_TYPE: &str = - "application/vnd.openxmlformats-officedocument.spreadsheetml.table+xml"; -pub(crate) const VBA_TYPE: &str = "application/vnd.ms-office.vbaProject"; -pub(crate) const VML_DRAWING_TYPE: &str = - "application/vnd.openxmlformats-officedocument.vmlDrawing"; -pub(crate) const WORKBOOK: &str = - "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; -pub(crate) const WORKBOOK_MACRO_TYPE: &str = "application/vnd.ms-excel.sheet.macroEnabled.main+xml"; -pub(crate) const WORKBOOK_TYPE: &str = - "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"; -pub(crate) const XPROPS_TYPE: &str = - "application/vnd.openxmlformats-officedocument.extended-properties+xml"; -pub(crate) const CUSTOM_PROPS_TYPE: &str = - "application/vnd.openxmlformats-officedocument.custom-properties+xml"; + ARC_APP => "docProps/app.xml", + ARC_CORE => "docProps/core.xml", + ARC_CUSTOM => "docProps/custom.xml", + CONTENT_TYPES => "[Content_Types].xml" +} -pub(crate) const PKG_CHARTS: &str = "xl/charts"; -pub(crate) const PKG_DRAWINGS: &str = "xl/drawings"; -pub(crate) const PKG_DRAWINGS_RELS: &str = "xl/drawings/_rels/drawing"; -pub(crate) const PKG_EMBEDDINGS: &str = "xl/embeddings"; -pub(crate) const PKG_MEDIA: &str = "xl/media"; -pub(crate) const PKG_PRNTR_SETTINGS: &str = "xl/printerSettings"; -pub(crate) const PKG_SHARED_STRINGS: &str = "xl/sharedStrings.xml"; -pub(crate) const PKG_SHEET: &str = "xl/worksheets/sheet"; -pub(crate) const PKG_SHEET_RELS: &str = "xl/worksheets/_rels/sheet"; -pub(crate) const PKG_STYLES: &str = "xl/styles.xml"; -pub(crate) const PKG_TABLES: &str = "xl/tables"; -pub(crate) const PKG_THEME: &str = "xl/theme/theme1.xml"; -pub(crate) const PKG_WORKBOOK: &str = "xl/workbook.xml"; -pub(crate) const PKG_WORKBOOK_RELS: &str = "xl/_rels/workbook.xml.rels"; -pub(crate) const PKG_VBA_PROJECT: &str = "xl/vbaProject.bin"; -pub(crate) const PKG_VML_DRAWING_RELS: &str = "xl/drawings/_rels/vmlDrawing"; - -pub(crate) const ARC_APP: &str = "docProps/app.xml"; -pub(crate) const ARC_CORE: &str = "docProps/core.xml"; -pub(crate) const ARC_CUSTOM: &str = "docProps/custom.xml"; - -pub(crate) const CONTENT_TYPES: &str = "[Content_Types].xml"; +// Ths file is ignored by rustfmt. diff --git a/src/helper/coordinate.rs b/src/helper/coordinate.rs index 33a4496b..403393cf 100644 --- a/src/helper/coordinate.rs +++ b/src/helper/coordinate.rs @@ -1,12 +1,28 @@ use std::iter::successors; -use fancy_regex::Regex; +use crate::helper::utils::compile_regex; +/// Converts a 1-based index to a string representation using letters +/// similar to Excel column naming (e.g., 1 -> "A", 27 -> "AA"). +/// +/// # Arguments +/// +/// * `index` - A 1-based index to convert to a string. Must be greater than or +/// equal to 1. +/// +/// # Returns +/// +/// A `String` representing the column name corresponding to the given index. +/// +/// # Panics +/// +/// Panics if the `index` is less than 1. fn index_to_alpha(index: u32) -> String { + const BASE_CHAR_CODE: u32 = 'A' as u32; // char 'A' + assert!(index >= 1, "Index cannot be less than one."); - const BASE_CHAR_CODE: u32 = 'A' as u32; - // below code is based on the source code of `radix_fmt` + // Generate the sequence of characters for the given index successors(Some(index - 1), |index| match index / 26u32 { 0 => None, n => Some(n - 1), @@ -19,29 +35,69 @@ fn index_to_alpha(index: u32) -> String { .collect() } +/// Converts a string representation of a column name (e.g., "A", "AA") +/// to its corresponding 1-based index. +/// +/// # Arguments +/// +/// * `alpha` - A string slice or reference to a string representing the column +/// name. +/// +/// # Returns +/// +/// A `u32` representing the 1-based index corresponding to the given column +/// name. +/// +/// # Examples +/// +/// ``` +/// let index = alpha_to_index("AA"); +/// assert_eq!(index, 27); +/// ``` fn alpha_to_index(alpha: S) -> u32 where S: AsRef, { const BASE_CHAR_CODE: u32 = 'A' as u32; - // since we only allow up to three characters, we can use pre-computed - /// powers of 26 `[26^0, 26^1, 26^2]` + // Pre-computed powers of 26 for up to three characters const POSITIONAL_CONSTANTS: [u32; 3] = [1, 26, 676]; alpha .as_ref() + .to_uppercase() .chars() .rev() .enumerate() .map(|(index, v)| { let vn = (v as u32 - BASE_CHAR_CODE) + 1; - // 26u32.pow(index as u32) * vn POSITIONAL_CONSTANTS[index] * vn }) .sum::() } +/// Converts a column name string to its corresponding 1-based index, +/// returning 0 if the input is "0". +/// +/// # Arguments +/// +/// * `column` - A string slice or reference to a string representing the column +/// name. +/// +/// # Returns +/// +/// A `u32` representing the 1-based index corresponding to the given column +/// name, or 0 if the input is "0". +/// +/// # Examples +/// +/// ``` +/// let index = column_index_from_string("AA"); +/// assert_eq!(index, 27); +/// +/// let index_zero = column_index_from_string("0"); +/// assert_eq!(index_zero, 0); +/// ``` #[inline] pub fn column_index_from_string>(column: S) -> u32 { let column_c = column.as_ref(); @@ -52,103 +108,290 @@ pub fn column_index_from_string>(column: S) -> u32 { alpha_to_index(column_c) } +/// Converts a 1-based column index to its corresponding Excel-style column +/// label. +/// +/// This function takes a column index starting from 1 and returns a string +/// representing the column label as used in Excel (e.g., 1 -> "A", 27 -> "AA"). +/// +/// # Parameters +/// +/// - `column_index`: A 1-based column index. Must be greater than or equal to +/// 1. +/// +/// # Returns +/// +/// A `String` representing the Excel-style column label. +/// +/// # Panics +/// +/// Panics if the `column_index` is less than 1, as column numbering starts from +/// 1. +/// +/// # Examples +/// +/// ``` +/// let label = string_from_column_index(1); +/// assert_eq!(label, "A"); +/// +/// let label = string_from_column_index(28); +/// assert_eq!(label, "AB"); +/// ``` #[inline] -pub fn string_from_column_index(column_index: &u32) -> String { - assert!(column_index >= &1u32, "Column number starts from 1."); +#[must_use] +pub fn string_from_column_index(column_index: u32) -> String { + assert!(column_index >= 1u32, "Column number starts from 1."); - index_to_alpha(*column_index) + index_to_alpha(column_index) } +/// Parses an Excel-style coordinate string into column and row indices, along +/// with lock flags. +/// +/// This function takes a coordinate string, which may include column letters +/// and row numbers, and optional '$' symbols indicating locked columns or rows. +/// It returns a tuple containing the column index, row index, and optional lock +/// flags for each. +/// +/// # Type Parameters +/// +/// - `T`: A type that can be referenced as a string slice, such as `&str` or +/// `String`. +/// +/// # Parameters +/// +/// - `coordinate`: The coordinate string to parse. It should be in the format +/// of Excel cell references, such as "A1", "$B$2", or "C$3". /// /// # Returns -/// A tuple with the column, and row address indexes and their respective lock flags. -///
-/// i.e. `(col, row, col_lock_flg, row_lock_flg)` -/// ## Note: -/// The minimum value for `col` and `row` is 1 +/// +/// A tuple `(Option, Option, Option, Option)` where: +/// - The first element is the column index, if present, calculated using +/// `alpha_to_index`. +/// - The second element is the row index, if present, parsed as a `u32`. +/// - The third element is a flag indicating if the column is locked, if +/// applicable. +/// - The fourth element is a flag indicating if the row is locked, if +/// applicable. +/// +/// # Examples +/// +/// ``` +/// let (col, row, col_lock, row_lock) = index_from_coordinate("$AB$12"); +/// assert_eq!(col, Some(28)); // 'AB' corresponds to column index 28 +/// assert_eq!(row, Some(12)); +/// assert_eq!(col_lock, Some(true)); +/// assert_eq!(row_lock, Some(true)); +/// ``` +/// +/// # Panics +/// +/// This function does not panic. It returns `None` for column or row indices if +/// they cannot be parsed. pub fn index_from_coordinate(coordinate: T) -> CellIndex where T: AsRef, { - lazy_static! { - static ref RE: Regex = Regex::new(r"((\$)?([A-Z]{1,3}))?((\$)?([0-9]+))?").unwrap(); - } - - let caps = RE.captures(coordinate.as_ref()).ok().flatten(); + let re = compile_regex!(r"((\$)?([A-Z]{1,3}))?((\$)?([0-9]+))?"); - caps.map(|v| { - let col = v.get(3).map(|v| alpha_to_index(v.as_str())); // col number: [A-Z]{1,3} - let row = v.get(6).and_then(|v| v.as_str().parse::().ok()); // row number: [0-9]+ + re.captures(coordinate.as_ref()) + .ok() + .flatten() + .map(|v| { + let col = v.get(3).map(|v| alpha_to_index(v.as_str())); // col number: [A-Z]{1,3} + let row = v.get(6).and_then(|v| v.as_str().parse::().ok()); // row number: [0-9]+ - let col_lock_flg = col.map(|_col| { - v.get(2).is_some() // col lock flag: (\$)? - }); + let col_lock_flg = col.map(|_col| { + v.get(2).is_some() // col lock flag: (\$)? + }); - let row_lock_flg = row.map(|_row| { - v.get(5).is_some() // row lock flag: (\$)? - }); + let row_lock_flg = row.map(|_row| { + v.get(5).is_some() // row lock flag: (\$)? + }); - (col, row, col_lock_flg, row_lock_flg) - }) - .unwrap_or_default() + (col, row, col_lock_flg, row_lock_flg) + }) + .unwrap_or_default() } +/// Converts a column index and row index into an Excel-style coordinate string. +/// +/// This function takes a 1-based column index and a row index, and returns a +/// string representing the coordinate in Excel format (e.g., (1, 1) -> "A1"). +/// +/// # Parameters +/// +/// - `col`: A 1-based column index. Must be greater than or equal to 1. +/// - `row`: A 1-based row index. Must be greater than or equal to 1. +/// +/// # Returns +/// +/// A `String` representing the Excel-style coordinate. +/// +/// # Examples +/// +/// ``` +/// let coordinate = coordinate_from_index(1, 1); +/// assert_eq!(coordinate, "A1"); +/// +/// let coordinate = coordinate_from_index(28, 3); +/// assert_eq!(coordinate, "AB3"); +/// ``` #[inline] -pub fn coordinate_from_index(col: &u32, row: &u32) -> String { +#[must_use] +pub fn coordinate_from_index(col: u32, row: u32) -> String { format!("{}{}", string_from_column_index(col), row) } +#[must_use] pub fn coordinate_from_index_with_lock( - col: &u32, - row: &u32, - is_lock_col: &bool, - is_lock_row: &bool, + col: u32, + row: u32, + is_lock_col: bool, + is_lock_row: bool, ) -> String { format!( "{}{}{}{}", - if *is_lock_col { "$" } else { "" }, + if is_lock_col { "$" } else { "" }, string_from_column_index(col), - if *is_lock_row { "$" } else { "" }, + if is_lock_row { "$" } else { "" }, row ) } +/// Adjusts a coordinate index by inserting an offset if conditions are met. +/// +/// This function checks if the given `num` is greater than or equal to +/// `root_num` and if `offset_num` is not zero. If both conditions are +/// satisfied, it returns the sum of `num` and `offset_num`. Otherwise, it +/// returns `num` unchanged. +/// +/// # Parameters +/// +/// - `num`: The original coordinate index to be adjusted. +/// - `root_num`: The threshold coordinate index for applying the offset. +/// - `offset_num`: The offset to be added to `num` if conditions are met. +/// +/// # Returns +/// +/// A `u32` representing the adjusted coordinate index. +/// +/// # Examples +/// +/// ``` +/// let adjusted = adjustment_insert_coordinate(5, 3, 2); +/// assert_eq!(adjusted, 7); // 5 + 2 since 5 >= 3 and offset is not zero +/// +/// let adjusted = adjustment_insert_coordinate(2, 3, 2); +/// assert_eq!(adjusted, 2); // No adjustment since 2 < 3 +/// ``` #[inline] -pub(crate) fn adjustment_insert_coordinate(num: &u32, root_num: &u32, offset_num: &u32) -> u32 { - if (num >= root_num && offset_num != &0) { +pub(crate) fn adjustment_insert_coordinate(num: u32, root_num: u32, offset_num: u32) -> u32 { + if num >= root_num && offset_num != 0 { num + offset_num } else { - *num + num } } +/// Adjusts a coordinate index by removing an offset if conditions are met. +/// +/// This function checks if the given `num` is greater than or equal to +/// `root_num` and if `offset_num` is not zero. If both conditions are +/// satisfied, it returns the result of subtracting `offset_num` from `num`. +/// Otherwise, it returns `num` unchanged. +/// +/// # Parameters +/// +/// - `num`: The original coordinate index to be adjusted. +/// - `root_num`: The threshold coordinate index for applying the offset. +/// - `offset_num`: The offset to be subtracted from `num` if conditions are +/// met. +/// +/// # Returns +/// +/// A `u32` representing the adjusted coordinate index. +/// +/// # Examples +/// +/// ``` +/// let adjusted = adjustment_remove_coordinate(5, 3, 2); +/// assert_eq!(adjusted, 3); // 5 - 2 since 5 >= 3 and offset is not zero +/// +/// let adjusted = adjustment_remove_coordinate(2, 3, 2); +/// assert_eq!(adjusted, 2); // No adjustment since 2 < 3 +/// ``` #[inline] -pub(crate) fn adjustment_remove_coordinate(num: &u32, root_num: &u32, offset_num: &u32) -> u32 { - if (num >= root_num && offset_num != &0) { +pub(crate) fn adjustment_remove_coordinate(num: u32, root_num: u32, offset_num: u32) -> u32 { + if num >= root_num && offset_num != 0 { num - offset_num } else { - *num + num } } +/// Determines if a coordinate index falls within a removable range. +/// +/// This function checks whether the given `num` is within the range defined by +/// `root_num` and `offset_num`. Specifically, it returns `true` if `num` is +/// greater than or equal to `root_num` and less than the sum of `root_num` and +/// `offset_num`, provided both `root_num` and `offset_num` are non-zero. +/// +/// # Parameters +/// +/// - `num`: The coordinate index to check. +/// - `root_num`: The starting index of the range. +/// - `offset_num`: The length of the range to be checked. +/// +/// # Returns +/// +/// A `bool` indicating whether `num` is within the removable range. +/// +/// # Examples +/// +/// ``` +/// let is_removable = is_remove_coordinate(5, 3, 2); +/// assert_eq!(is_removable, true); // 5 is within the range [3, 5) +/// +/// let is_removable = is_remove_coordinate(6, 3, 2); +/// assert_eq!(is_removable, false); // 6 is outside the range [3, 5) +/// ``` #[inline] -pub(crate) fn is_remove_coordinate(num: &u32, root_num: &u32, offset_num: &u32) -> bool { - if root_num != &0 && offset_num != &0 { - return num >= root_num && num < &(root_num + offset_num); +pub(crate) fn is_remove_coordinate(num: u32, root_num: u32, offset_num: u32) -> bool { + if root_num != 0 && offset_num != 0 { + return num >= root_num && num < (root_num + offset_num); } false } +/// Type alias for a tuple representing cell index information. +/// +/// This tuple contains: +/// - An optional column index (`Option`). +/// - An optional row index (`Option`). +/// - An optional column lock flag (`Option`). +/// - An optional row lock flag (`Option`). pub type CellIndex = (Option, Option, Option, Option); -/// Struct for representing cell coordinates with row and column numbers +/// Struct for representing cell coordinates with row and column numbers. #[derive(Clone, Debug)] pub struct CellCoordinates { - pub row: u32, - pub col: u32, + pub row: u32, // The 1-based row index of the cell. + pub col: u32, // The 1-based column index of the cell. } impl CellCoordinates { + /// Creates a new `CellCoordinates` instance with the specified column and + /// row indices. + /// + /// # Parameters + /// + /// - `col`: The 1-based column index. + /// - `row`: The 1-based row index. + /// + /// # Returns + /// + /// A new `CellCoordinates` instance. #[inline] fn new(col: u32, row: u32) -> Self { CellCoordinates { row, col } @@ -156,28 +399,55 @@ impl CellCoordinates { } impl From<(u32, u32)> for CellCoordinates { + /// Converts a tuple of column and row indices into a `CellCoordinates` + /// instance. + /// + /// # Parameters + /// + /// - `value`: A tuple containing the 1-based column and row indices. + /// + /// # Returns + /// + /// A `CellCoordinates` instance representing the specified indices. #[inline] fn from(value: (u32, u32)) -> Self { CellCoordinates::new(value.0, value.1) } } -impl From<(&u32, &u32)> for CellCoordinates { - #[inline] - fn from(value: (&u32, &u32)) -> Self { - CellCoordinates::new(*value.0, *value.1) - } -} - impl From for CellCoordinates { + /// Converts a string representation of a cell coordinate into a + /// `CellCoordinates` instance. + /// + /// The string is expected to be in Excel-style format (e.g., "A1"). + /// + /// # Parameters + /// + /// - `value`: A string containing the cell coordinate. + /// + /// # Returns + /// + /// A `CellCoordinates` instance representing the specified coordinate. #[inline] fn from(value: String) -> Self { - let str_ref: &str = value.as_ref(); - str_ref.into() + value.as_str().into() } } impl From<&str> for CellCoordinates { + /// Converts a string slice representation of a cell coordinate into a + /// `CellCoordinates` instance. + /// + /// The string is expected to be in Excel-style format (e.g., "A1"). The + /// function converts the string to uppercase before parsing. + /// + /// # Parameters + /// + /// - `value`: A string slice containing the cell coordinate. + /// + /// # Returns + /// + /// A `CellCoordinates` instance representing the specified coordinate. #[inline] fn from(value: &str) -> Self { let (col, row, ..) = index_from_coordinate(value.to_uppercase()); @@ -200,20 +470,21 @@ mod tests { assert_eq!(column_index_from_string("ZZ"), 702); assert_eq!(column_index_from_string("AAA"), 703); assert_eq!(column_index_from_string("LAV"), 8160); - assert_eq!(column_index_from_string("XFD"), 16384); // Max. supported by Excel 2102 + assert_eq!(column_index_from_string("XFD"), 16384); // Max. supported by + // Excel 2012 } #[test] fn string_from_column_index_1() { - assert_eq!(string_from_column_index(&1), String::from("A")); - assert_eq!(string_from_column_index(&26), String::from("Z")); - assert_eq!(string_from_column_index(&27), String::from("AA")); - assert_eq!(string_from_column_index(&28), String::from("AB")); - assert_eq!(string_from_column_index(&53), String::from("BA")); - assert_eq!(string_from_column_index(&702), String::from("ZZ")); - assert_eq!(string_from_column_index(&703), String::from("AAA")); - assert_eq!(string_from_column_index(&8160), String::from("LAV")); - assert_eq!(string_from_column_index(&16384), String::from("XFD")); + assert_eq!(string_from_column_index(1), String::from("A")); + assert_eq!(string_from_column_index(26), String::from("Z")); + assert_eq!(string_from_column_index(27), String::from("AA")); + assert_eq!(string_from_column_index(28), String::from("AB")); + assert_eq!(string_from_column_index(53), String::from("BA")); + assert_eq!(string_from_column_index(702), String::from("ZZ")); + assert_eq!(string_from_column_index(703), String::from("AAA")); + assert_eq!(string_from_column_index(8160), String::from("LAV")); + assert_eq!(string_from_column_index(16384), String::from("XFD")); } #[test] diff --git a/src/helper/crypt.rs b/src/helper/crypt.rs deleted file mode 100644 index 2e7847c9..00000000 --- a/src/helper/crypt.rs +++ /dev/null @@ -1,936 +0,0 @@ -use super::const_str::*; -use crate::structs::SheetProtection; -use crate::structs::WorkbookProtection; -use crate::writer::driver::*; -use aes::cipher::{block_padding::NoPadding, BlockEncryptMut, KeyIvInit}; -use base64::{engine::general_purpose::STANDARD, Engine as _}; -use byteorder::{ByteOrder, LittleEndian}; -use cfb; -use hmac::{Hmac, Mac}; -use quick_xml::events::{BytesDecl, Event}; -use quick_xml::Writer; -use sha2::{Digest, Sha512}; -use std::cmp::Ordering; -use std::io; -use std::io::Write; -use std::path::Path; - -type Aes256CbcEnc = cbc::Encryptor; - -const ENCRYPTION_INFO_PREFIX: &[u8] = &[0x04, 0x00, 0x04, 0x00, 0x40, 0x00, 0x00, 0x00]; // First 4 bytes are the version number, second 4 bytes are reserved. -const PACKAGE_ENCRYPTION_CHUNK_SIZE: usize = 4096; -const PACKAGE_OFFSET: usize = 8; // First 8 bytes are the size of the stream -const BLOCK_KEYS_DATA_INTEGRITY_HMAC_KEY: &[u8] = &[0x5f, 0xb2, 0xad, 0x01, 0x0c, 0xb9, 0xe1, 0xf6]; -const BLOCK_KEYS_DATA_INTEGRITY_HMAC_VALUE: &[u8] = - &[0xa0, 0x67, 0x7f, 0x02, 0xb2, 0x2c, 0x84, 0x33]; -const BLOCK_KEYS_KEY: &[u8] = &[0x14, 0x6e, 0x0b, 0xe7, 0xab, 0xac, 0xd0, 0xd6]; -const BLOCK_VERIFIER_HASH_INPUT: &[u8] = &[0xfe, 0xa7, 0xd2, 0x76, 0x3b, 0x4b, 0x9e, 0x79]; -const BLOCK_VERIFIER_HASH_VALUE: &[u8] = &[0xd7, 0xaa, 0x0f, 0x6d, 0x30, 0x61, 0x34, 0x4e]; - -pub fn encrypt_sheet_protection(password: &str, sheet_protection: &mut SheetProtection) { - let key_salt_value = gen_random_16(); - let key_hash_algorithm = "SHA-512"; - let key_spin_count = 100000; - - let key = convert_password_to_hash( - password, - key_hash_algorithm, - &key_salt_value, - &key_spin_count, - ); - - let salt_value_str = STANDARD.encode(key_salt_value); - let hash_value_str = STANDARD.encode(key); - - sheet_protection.set_algorithm_name(key_hash_algorithm); - sheet_protection.set_salt_value(salt_value_str); - sheet_protection.set_spin_count(key_spin_count as u32); - sheet_protection.set_hash_value(hash_value_str); - sheet_protection.remove_password_raw(); -} - -pub fn encrypt_workbook_protection(password: &str, workbook_protection: &mut WorkbookProtection) { - let key_salt_value = gen_random_16(); - let key_hash_algorithm = "SHA-512"; - let key_spin_count = 100000; - - let key = convert_password_to_hash( - password, - key_hash_algorithm, - &key_salt_value, - &key_spin_count, - ); - - let salt_value_str = STANDARD.encode(key_salt_value); - let hash_value_str = STANDARD.encode(key); - - workbook_protection.set_workbook_algorithm_name(key_hash_algorithm); - workbook_protection.set_workbook_salt_value(salt_value_str); - workbook_protection.set_workbook_spin_count(key_spin_count as u32); - workbook_protection.set_workbook_hash_value(hash_value_str); - workbook_protection.remove_workbook_password_raw(); -} - -pub fn encrypt_revisions_protection(password: &str, workbook_protection: &mut WorkbookProtection) { - let key_salt_value = gen_random_16(); - let key_hash_algorithm = "SHA-512"; - let key_spin_count = 100000; - - let key = convert_password_to_hash( - password, - key_hash_algorithm, - &key_salt_value, - &key_spin_count, - ); - - let salt_value_str = STANDARD.encode(key_salt_value); - let hash_value_str = STANDARD.encode(key); - - workbook_protection.set_revisions_algorithm_name(key_hash_algorithm); - workbook_protection.set_revisions_salt_value(salt_value_str); - workbook_protection.set_revisions_spin_count(key_spin_count as u32); - workbook_protection.set_revisions_hash_value(hash_value_str); - workbook_protection.remove_revisions_password_raw(); -} - -pub fn encrypt>(filepath: &P, data: &[u8], password: &str) { - // package params - let package_key = gen_random_32(); - let package_cipher_algorithm = "AES"; - let package_cipher_chaining = "ChainingModeCBC"; - let package_salt_value = gen_random_16(); - let package_hash_algorithm = "SHA512"; - let package_hash_size = 64; - let package_block_size = 16; - let package_key_bits = package_key.len() * 8; - - // key params - let key_cipher_algorithm = "AES"; - let key_cipher_chaining = "ChainingModeCBC"; - let key_salt_value = gen_random_16(); - let key_hash_algorithm = "SHA512"; - let key_hash_size = 64; - let key_block_size = 16; - let key_spin_count = 100000; - let key_key_bits = 256; - - // encrypted_package - let encrypted_package = crypt_package( - &true, - package_cipher_algorithm, - package_cipher_chaining, - package_hash_algorithm, - &package_block_size, - &package_salt_value, - &package_key, - data, - ); - - // hmac key - let hmac_key = gen_random_64(); - let hmac_key_iv = create_iv( - package_hash_algorithm, - &package_salt_value, - &package_block_size, - &BLOCK_KEYS_DATA_INTEGRITY_HMAC_KEY.to_vec(), - ); - let encrypted_hmac_key = crypt( - &true, - package_cipher_algorithm, - package_cipher_chaining, - &package_key, - &hmac_key_iv, - &hmac_key, - ) - .unwrap(); - - // hmac value - let hmac_value = hmac(package_hash_algorithm, &hmac_key, vec![&encrypted_package]).unwrap(); - let hmac_value_iv = create_iv( - package_hash_algorithm, - &package_salt_value, - &package_block_size, - &BLOCK_KEYS_DATA_INTEGRITY_HMAC_VALUE.to_vec(), - ); - let encrypted_hmac_value = crypt( - &true, - package_cipher_algorithm, - package_cipher_chaining, - &package_key, - &hmac_value_iv, - &hmac_value, - ) - .unwrap(); - - // key - let key = convert_password_to_key( - password, - key_hash_algorithm, - &key_salt_value, - &key_spin_count, - &key_key_bits, - &BLOCK_KEYS_KEY.to_vec(), - ); - let encrypted_key_value = crypt( - &true, - key_cipher_algorithm, - key_cipher_chaining, - &key, - &key_salt_value, - &package_key, - ) - .unwrap(); - - // verifier_hash_input - let verifier_hash_input = gen_random_16(); - let verifier_hash_input_key = convert_password_to_key( - password, - key_hash_algorithm, - &key_salt_value, - &key_spin_count, - &key_key_bits, - &BLOCK_VERIFIER_HASH_INPUT.to_vec(), - ); - let encrypted_verifier_hash_input = crypt( - &true, - key_cipher_algorithm, - key_cipher_chaining, - &verifier_hash_input_key, - &key_salt_value, - &verifier_hash_input, - ) - .unwrap(); - - // verifier_hash_value - let verifier_hash_value = hash(key_hash_algorithm, vec![&verifier_hash_input]).unwrap(); - let verifier_hash_value_key = convert_password_to_key( - password, - key_hash_algorithm, - &key_salt_value, - &key_spin_count, - &key_key_bits, - &BLOCK_VERIFIER_HASH_VALUE.to_vec(), - ); - let encrypted_verifier_hash_value = crypt( - &true, - key_cipher_algorithm, - key_cipher_chaining, - &verifier_hash_value_key, - &key_salt_value, - &verifier_hash_value, - ) - .unwrap(); - - // XML - let encryption_info_buffer = build_encryption_info( - &package_salt_value, - &package_block_size, - &package_key_bits, - &package_hash_size, - package_cipher_algorithm, - package_cipher_chaining, - package_hash_algorithm, - &encrypted_hmac_key, - &encrypted_hmac_value, - &key_spin_count, - &key_salt_value, - &key_block_size, - &key_key_bits, - &key_hash_size, - key_cipher_algorithm, - key_cipher_chaining, - key_hash_algorithm, - &encrypted_verifier_hash_input, - &encrypted_verifier_hash_value, - &encrypted_key_value, - ); - - let mut comp = cfb::create(filepath).unwrap(); - { - let mut stream_info = comp.create_stream("EncryptionInfo").unwrap(); - stream_info.write_all(&encryption_info_buffer).unwrap(); - } - { - let mut stream_package = comp.create_stream("EncryptedPackage").unwrap(); - stream_package.write_all(&encrypted_package).unwrap(); - } -} - -// Encrypt/decrypt the package -#[allow(clippy::too_many_arguments)] -fn crypt_package( - encrypt: &bool, - cipher_algorithm: &str, - cipher_chaining: &str, - hash_algorithm: &str, - block_size: &usize, - salt_value: &[u8], - key: &[u8], - input: &[u8], -) -> Vec { - // The first 8 bytes is supposed to be the length, but it seems like it is really the length - 4.. - let mut output_chunks: Vec> = Vec::new(); - let offset = if encrypt == &true { 0 } else { PACKAGE_OFFSET }; - - // The package is encoded in chunks. Encrypt/decrypt each and concat. - let mut i: usize = 0; - let mut end = 0; - while end < input.len() { - let start = end; - end = start + PACKAGE_ENCRYPTION_CHUNK_SIZE; - if end > input.len() { - end = input.len(); - }; - - // Grab the next chunk - let mut input_chunk = buffer_slice(input, start + offset, end + offset); - - // Pad the chunk if it is not an integer multiple of the block size - let remainder = input_chunk.len() % block_size; - if remainder > 0 { - let buffer = buffer_alloc(0, block_size - remainder); - input_chunk = buffer_concat(vec![&input_chunk, &buffer]); - } - - // Create the initialization vector - // Create the block key from the current index - let block_key_buffer = create_uint32_le_buffer(&(i as u32), None); - let iv = create_iv(hash_algorithm, salt_value, block_size, &block_key_buffer); - - // Encrypt/decrypt the chunk and add it to the array - let output_chunk = crypt( - encrypt, - cipher_algorithm, - cipher_chaining, - key, - &iv, - &input_chunk, - ) - .unwrap(); - output_chunks.push(output_chunk); - - i += 1; - } - - // Concat all of the output chunks. - let output_chunks_as: Vec<_> = output_chunks.iter().map(AsRef::as_ref).collect(); - let mut output = buffer_concat(output_chunks_as); - - if *encrypt { - // Put the length of the package in the first 8 bytes - let input_len = input.len(); - output = buffer_concat(vec![ - &create_uint32_le_buffer(&(input_len as u32), Some(&PACKAGE_OFFSET)), - &output, - ]); - } else { - // Truncate the buffer to the size in the prefix - let length = buffer_read_u_int32_le(input, &0); - output = output[0..length as usize].to_vec(); - } - - output -} - -// Create an initialization vector (IV) -fn create_iv( - hash_algorithm: &str, - salt_value: &[u8], - block_size: &usize, - block_key: &[u8], -) -> Vec { - // Create the initialization vector by hashing the salt with the block key. - // Truncate or pad as needed to meet the block size. - let mut iv = hash(hash_algorithm, vec![salt_value, block_key]).unwrap(); - match iv.len().cmp(block_size) { - Ordering::Less => { - let mut tmp = buffer_alloc(0x36, *block_size); - buffer_copy(&mut tmp, &iv); - iv = tmp; - } - Ordering::Greater => { - iv = buffer_slice(&iv, 0, *block_size); - } - _ => {} - } - iv -} - -// Encrypt/decrypt input -fn crypt( - _encrypt: &bool, - _cipher_algorithm: &str, - _cipher_chaining: &str, - key: &[u8], - iv: &[u8], - input: &[u8], -) -> Result, String> { - let mut buf = [0u8; 4096]; - let pt_len = input.len(); - buf[..pt_len].copy_from_slice(input); - let ct = match key.len() * 8 { - 256 => Aes256CbcEnc::new_from_slices(key, iv) - .unwrap() - .encrypt_padded_mut::(&mut buf, pt_len) - .unwrap(), - _ => { - return Err("key size not supported!".to_string()); - } - }; - Ok(ct.to_vec()) -} - -fn hmac(algorithm: &str, key: &[u8], buffers: Vec<&[u8]>) -> Result, String> { - let mut mac = match algorithm { - "SHA512" => { - type HmacSha512 = Hmac; - HmacSha512::new_from_slice(key).unwrap() - } - _ => { - return Err(format!("algorithm {} not supported!", algorithm)); - } - }; - mac.update(&buffer_concat(buffers)); - - let result = mac.finalize(); - Ok(result.into_bytes()[..].to_vec()) -} - -fn convert_password_to_key( - password: &str, - hash_algorithm: &str, - salt_value: &[u8], - spin_count: &usize, - key_bits: &usize, - block_key: &[u8], -) -> Vec { - // Password must be in unicode buffer - let mut password_buffer: Vec = Vec::new(); - let v: Vec = password.encode_utf16().collect(); - for a in v { - let d = a.to_le_bytes(); - password_buffer.push(d[0]); - password_buffer.push(d[1]); - } - - // Generate the initial hash - let mut key = hash(hash_algorithm, vec![salt_value, &password_buffer]).unwrap(); - - // Now regenerate until spin count - for i in 0..*spin_count { - let iterator = create_uint32_le_buffer(&(i as u32), None); - key = hash(hash_algorithm, vec![&iterator, &key]).unwrap(); - } - - // Now generate the final hash - key = hash(hash_algorithm, vec![&key, block_key]).unwrap(); - - // Truncate or pad as needed to get to length of keyBits - let key_bytes = key_bits / 8; - match key.len().cmp(&key_bytes) { - Ordering::Less => { - let mut tmp = buffer_alloc(0x36, key_bytes); - buffer_copy(&mut tmp, &key); - tmp - } - Ordering::Greater => buffer_slice(&key, 0, key_bytes), - _ => key, - } -} - -fn convert_password_to_hash( - password: &str, - hash_algorithm: &str, - salt_value: &[u8], - spin_count: &usize, -) -> Vec { - // Password must be in unicode buffer - let mut password_buffer: Vec = Vec::new(); - let v: Vec = password.encode_utf16().collect(); - for a in v { - let d = a.to_le_bytes(); - password_buffer.push(d[0]); - password_buffer.push(d[1]); - } - - // Generate the initial hash - let mut key = hash(hash_algorithm, vec![salt_value, &password_buffer]).unwrap(); - - // Now regenerate until spin count - for i in 0..*spin_count { - let iterator = create_uint32_le_buffer(&(i as u32), None); - key = hash(hash_algorithm, vec![&key, &iterator]).unwrap(); - } - - key -} - -// Calculate a hash of the concatenated buffers with the given algorithm. -fn hash(algorithm: &str, buffers: Vec<&[u8]>) -> Result, String> { - let mut digest = match algorithm { - "SHA512" | "SHA-512" => Sha512::new(), - _ => { - return Err(format!("algorithm {} not supported!", algorithm)); - } - }; - digest.update(&buffer_concat(buffers)[..]); - Ok(digest.finalize().to_vec()) -} - -#[inline] -fn gen_random_16() -> Vec { - let buf: &mut [u8] = &mut [0; 16]; - getrandom::getrandom(buf); - buf.to_vec() -} - -#[inline] -fn gen_random_32() -> Vec { - let buf: &mut [u8] = &mut [0; 32]; - getrandom::getrandom(buf); - buf.to_vec() -} - -#[inline] -fn gen_random_64() -> Vec { - let buf: &mut [u8] = &mut [0; 64]; - getrandom::getrandom(buf); - buf.to_vec() -} - -// Create a buffer of an integer encoded as a uint32le -#[inline] -fn create_uint32_le_buffer(value: &u32, buffer_size: Option<&usize>) -> Vec { - let bs_prm = buffer_size.unwrap_or(&4); - let mut buffer = buffer_alloc(0, *bs_prm); - buffer_write_u_int32_le(&mut buffer, value, &0); - buffer -} - -#[allow(clippy::too_many_arguments)] -fn build_encryption_info( - package_salt_value: &[u8], - package_block_size: &usize, - package_key_bits: &usize, - package_hash_size: &usize, - package_cipher_algorithm: &str, - package_cipher_chaining: &str, - package_hash_algorithm: &str, - data_integrity_encrypted_hmac_key: &[u8], - data_integrity_encrypted_hmac_value: &[u8], - key_spin_count: &usize, - key_salt_value: &[u8], - key_block_size: &usize, - key_key_bits: &usize, - key_hash_size: &usize, - key_cipher_algorithm: &str, - key_cipher_chaining: &str, - key_hash_algorithm: &str, - key_encrypted_verifier_hash_input: &[u8], - key_encrypted_verifier_hash_value: &[u8], - key_encrypted_key_value: &[u8], -) -> Vec { - let mut writer = Writer::new(io::Cursor::new(Vec::new())); - // XML header - writer.write_event(Event::Decl(BytesDecl::new( - "1.0", - Some("UTF-8"), - Some("yes"), - ))); - write_new_line(&mut writer); - - // Map the object into the appropriate XML structure. Buffers are encoded in base 64. - write_start_tag( - &mut writer, - "encryption", - vec![ - ("xmlns", ENCRYPTION_NS), - ("xmlns:p", PASSWORD_NS), - ("xmlns:c", CERTIFICATE_NS), - ], - false, - ); - let str_package_salt_value_len = package_salt_value.len().to_string(); - let str_package_block_size = package_block_size.to_string(); - let str_package_key_bits = package_key_bits.to_string(); - let str_package_hash_size = package_hash_size.to_string(); - let str_package_salt_value = STANDARD.encode(package_salt_value); - write_start_tag( - &mut writer, - "keyData", - vec![ - ("saltSize", &str_package_salt_value_len), - ("blockSize", &str_package_block_size), - ("keyBits", &str_package_key_bits), - ("hashSize", &str_package_hash_size), - ("cipherAlgorithm", package_cipher_algorithm), - ("cipherChaining", package_cipher_chaining), - ("hashAlgorithm", package_hash_algorithm), - ("saltValue", &str_package_salt_value), - ], - true, - ); - let str_data_integrity_encrypted_hmac_key = STANDARD.encode(data_integrity_encrypted_hmac_key); - let str_data_integrity_encrypted_hmac_value = - STANDARD.encode(data_integrity_encrypted_hmac_value); - write_start_tag( - &mut writer, - "dataIntegrity", - vec![ - ("encryptedHmacKey", &str_data_integrity_encrypted_hmac_key), - ( - "encryptedHmacValue", - &str_data_integrity_encrypted_hmac_value, - ), - ], - true, - ); - write_start_tag(&mut writer, "keyEncryptors", vec![], false); - write_start_tag( - &mut writer, - "keyEncryptor", - vec![("uri", PASSWORD_NS)], - false, - ); - let str_key_spin_count = key_spin_count.to_string(); - let str_key_salt_value_len = key_salt_value.len().to_string(); - let str_key_block_size = key_block_size.to_string(); - let str_key_key_bits = key_key_bits.to_string(); - let str_key_hash_size = key_hash_size.to_string(); - let str_key_salt_value = STANDARD.encode(key_salt_value); - let str_key_encrypted_verifier_hash_input = STANDARD.encode(key_encrypted_verifier_hash_input); - let str_key_encrypted_verifier_hash_value = STANDARD.encode(key_encrypted_verifier_hash_value); - let str_key_key_encrypted_key_value = STANDARD.encode(key_encrypted_key_value); - write_start_tag( - &mut writer, - "p:encryptedKey", - vec![ - ("spinCount", &str_key_spin_count), - ("saltSize", &str_key_salt_value_len), - ("blockSize", &str_key_block_size), - ("keyBits", &str_key_key_bits), - ("hashSize", &str_key_hash_size), - ("cipherAlgorithm", key_cipher_algorithm), - ("cipherChaining", key_cipher_chaining), - ("hashAlgorithm", key_hash_algorithm), - ("saltValue", &str_key_salt_value), - ( - "encryptedVerifierHashInput", - &str_key_encrypted_verifier_hash_input, - ), - ( - "encryptedVerifierHashValue", - &str_key_encrypted_verifier_hash_value, - ), - ("encryptedKeyValue", &str_key_key_encrypted_key_value), - ], - true, - ); - - write_end_tag(&mut writer, "keyEncryptor"); - write_end_tag(&mut writer, "keyEncryptors"); - write_end_tag(&mut writer, "encryption"); - - let result = writer.into_inner().into_inner().to_vec(); - buffer_concat(vec![&ENCRYPTION_INFO_PREFIX.to_vec(), &result]) -} - -#[inline] -fn buffer_slice(buffer: &[u8], start: usize, end: usize) -> Vec { - buffer[start..end].to_vec() -} - -#[inline] -fn buffer_alloc(alloc_char: u8, size: usize) -> Vec { - vec![alloc_char; size] -} - -fn buffer_concat(buffers: Vec<&[u8]>) -> Vec { - let mut result: Vec = Vec::new(); - for buffer in buffers { - result.extend(buffer); - } - result -} -fn buffer_copy(buffer1: &mut [u8], buffer2: &[u8]) { - for (i, byte) in buffer2.iter().enumerate() { - std::mem::replace(&mut buffer1[i], *byte); - } -} - -#[inline] -fn buffer_read_u_int32_le(buffer: &[u8], _cnt: &usize) -> u32 { - LittleEndian::read_u32(buffer) -} - -#[inline] -fn buffer_write_u_int32_le(buffer: &mut [u8], value: &u32, _cnt: &usize) { - LittleEndian::write_u32(buffer, *value); -} - -#[cfg(test)] -mod tests { - use super::*; - use std::fs::File; - use std::io::Read; - use std::{fmt::Write, num::ParseIntError}; - - fn decode_hex(s: &str) -> Result, ParseIntError> { - (0..s.len()) - .step_by(2) - .map(|i| u8::from_str_radix(&s[i..i + 2], 16)) - .collect() - } - - fn encode_hex(bytes: &[u8]) -> String { - let mut s = String::with_capacity(bytes.len() * 2); - for &b in bytes { - write!(&mut s, "{:02x}", b).unwrap(); - } - s - } - - #[test] - fn test_encrypt() { - let mut file = File::open("./tests/test_files/aaa.xlsx").unwrap(); - let mut data = Vec::new(); - let _ = file.read_to_end(&mut data).unwrap(); - - let password = "password"; - - // package params - let package_key = - decode_hex("cdf9defae2480933c503350e16334453d1cb8348bb2fea585db7f9e1f78fe9bf").unwrap(); - let package_cipher_algorithm = "AES"; - let package_cipher_chaining = "ChainingModeCBC"; - let package_salt_value = decode_hex("4c251b321d85cecfcb6d952ba6d81846").unwrap(); - let package_hash_algorithm = "SHA512"; - let package_hash_size = 64; - let package_block_size = 16; - let package_key_bits = package_key.len() * 8; - - // key params - let key_cipher_algorithm = "AES"; - let key_cipher_chaining = "ChainingModeCBC"; - let key_salt_value = decode_hex("3aa973eec73c98c4710021730ef5b513").unwrap(); - let key_hash_algorithm = "SHA512"; - let key_hash_size = 64; - let key_block_size = 16; - let key_spin_count = 100000; - let key_key_bits = 256; - - // encrypted_package - let encrypted_package = crypt_package( - &true, - package_cipher_algorithm, - package_cipher_chaining, - package_hash_algorithm, - &package_block_size, - &package_salt_value, - &package_key, - &data, - ); - - // hmac key - let hmac_key = decode_hex("4c6e4db6d9a60e5d41c3ca639a682aaa71da7437202fe92ec5d814bd1e9e4e6a831aee889eae3bc18bc1bebedae1f73393fddfffd0a0b6c557485fefcdb5e98b").unwrap(); - let hmac_key_iv = create_iv( - package_hash_algorithm, - &package_salt_value, - &package_block_size, - &BLOCK_KEYS_DATA_INTEGRITY_HMAC_KEY.to_vec(), - ); - let converted = encode_hex(&hmac_key_iv); - assert_eq!(&converted, "ba1bf00eed82b07ee65e574eb1f46043"); - - let encrypted_hmac_key = crypt( - &true, - package_cipher_algorithm, - package_cipher_chaining, - &package_key, - &hmac_key_iv, - &hmac_key, - ) - .unwrap(); - let converted = encode_hex(&encrypted_hmac_key); - assert_eq!(&converted, "b32b1cdc4ac1af244377c1eb57efd31a819f555a7204adcc0cfe364b394bbdb086a8daef4f4c512d52e3db6a54b1d45e1dd1dbfa3ddacc29fe35449ba5225dc7"); - - // hmac value - let hmac_value = hmac(package_hash_algorithm, &hmac_key, vec![&encrypted_package]).unwrap(); - //let converted = encode_hex(&hmac_value); - //assert_eq!(&converted, "41748c1ed0bcbbc46301a0a21e00747b6fafaa52ddbe4952a77a399ed4514b40c9b7e59f1c52c4cc72881794435336cc6e42fef4498245575bb9c2343480773f"); - - let hmac_value_iv = create_iv( - package_hash_algorithm, - &package_salt_value, - &package_block_size, - &BLOCK_KEYS_DATA_INTEGRITY_HMAC_VALUE.to_vec(), - ); - let converted = encode_hex(&hmac_value_iv); - assert_eq!(&converted, "088385b871292e7ed8414f173c5b6622"); - - let encrypted_hmac_value = crypt( - &true, - package_cipher_algorithm, - package_cipher_chaining, - &package_key, - &hmac_value_iv, - &hmac_value, - ) - .unwrap(); - //let converted = encode_hex(&encrypted_hmac_value); - //assert_eq!(&converted, "1f6fc2877101ac12ccee6dbb0e5ea2556cc61c2c532b89ffc701fd16c5078e7e8264034ded6dc00469039f706fce12747db817574f13b49d18e914fdf4e3e93b"); - - // key - let key = convert_password_to_key( - password, - key_hash_algorithm, - &key_salt_value, - &key_spin_count, - &key_key_bits, - &BLOCK_KEYS_KEY.to_vec(), - ); - let converted = encode_hex(&key); - assert_eq!( - &converted, - "8d5869311b1c1fdb59a1de6fe1e6f2ce7dccd4deb198a6dfb1f7fb55bc03487d" - ); - - let encrypted_key_value = crypt( - &true, - key_cipher_algorithm, - key_cipher_chaining, - &key, - &key_salt_value, - &package_key, - ) - .unwrap(); - let converted = encode_hex(&encrypted_key_value); - assert_eq!( - &converted, - "5017ddc6146e56dfbf76734b3e99b80f36a4c9a2e9eb21fe77695f73850cc452" - ); - - // verifier_hash_input - let verifier_hash_input = "8f54777cba87efa55ea2db8399873815".as_bytes().to_vec(); - let verifier_hash_input_key = convert_password_to_key( - password, - key_hash_algorithm, - &key_salt_value, - &key_spin_count, - &key_key_bits, - &BLOCK_VERIFIER_HASH_INPUT.to_vec(), - ); - let converted = encode_hex(&verifier_hash_input_key); - assert_eq!( - &converted, - "44e4b664c512b08e7577aa3fc7e11ad603e0877a476931fad5aa79e203304aff" - ); - - let encrypted_verifier_hash_input = crypt( - &true, - key_cipher_algorithm, - key_cipher_chaining, - &verifier_hash_input_key, - &key_salt_value, - &verifier_hash_input, - ) - .unwrap(); - //let converted = encode_hex(&encrypted_verifier_hash_input); - //assert_eq!(&converted, "2fb9eea58e227ffa549449e941f1199e"); - - // verifier_hash_value - let verifier_hash_value = hash(key_hash_algorithm, vec![&verifier_hash_input]).unwrap(); - //let converted = encode_hex(&verifier_hash_value); - //assert_eq!(&converted, "920b1de74f38d9cb3ccb3394119ed37e958404fdc47560b1bf647d3c49c22549625fe4a0bd36798bd68a0d98ae64f6ab64a330c9890c62bb740aa492c226ae1f"); - - let verifier_hash_value_key = convert_password_to_key( - password, - key_hash_algorithm, - &key_salt_value, - &key_spin_count, - &key_key_bits, - &BLOCK_VERIFIER_HASH_VALUE.to_vec(), - ); - //let converted = encode_hex(&verifier_hash_value_key); - //assert_eq!(&converted, "d5515a6062e3e99551b80b92db1fe646483884cdb63e1e7595a9f2cca7532884"); - - let encrypted_verifier_hash_value = crypt( - &true, - key_cipher_algorithm, - key_cipher_chaining, - &verifier_hash_value_key, - &key_salt_value, - &verifier_hash_value, - ) - .unwrap(); - //let converted = encode_hex(&encrypted_verifier_hash_value); - //assert_eq!(&converted, "0d9c888111b40b630b739c95a5f5b6be67c8f96acdd1bee185bd808b507f652760a2e77f63a6ad0c46f985f2bb8dab4fcf9b86d6a40d9c21299bb4ddf788b250"); - - // XML - let _ = build_encryption_info( - &package_salt_value, - &package_block_size, - &package_key_bits, - &package_hash_size, - package_cipher_algorithm, - package_cipher_chaining, - package_hash_algorithm, - &encrypted_hmac_key, - &encrypted_hmac_value, - &key_spin_count, - &key_salt_value, - &key_block_size, - &key_key_bits, - &key_hash_size, - key_cipher_algorithm, - key_cipher_chaining, - key_hash_algorithm, - &encrypted_verifier_hash_input, - &encrypted_verifier_hash_value, - &encrypted_key_value, - ); - } - - #[test] - fn test_hash() { - let package_salt_value = decode_hex("4c251b321d85cecfcb6d952ba6d81846").unwrap(); - let result = hash( - "SHA512", - vec![ - &package_salt_value, - &BLOCK_KEYS_DATA_INTEGRITY_HMAC_KEY.to_vec(), - ], - ) - .unwrap(); - let converted = encode_hex(&result); - assert_eq!(&converted, "ba1bf00eed82b07ee65e574eb1f460435d2a1405e81904fd01d5ed5adf43fdcfd8e9aeebad0c08065e0db20cdc8e4552744b61ad1b3cf9a3c5aad5b2a047e76b"); - } - - #[test] - fn test_buffer_slice() { - let buffer = decode_hex("ba1bf00eed82b07ee65e574eb1f460435d2a1405e81904fd01d5ed5adf43fdcfd8e9aeebad0c08065e0db20cdc8e4552744b61ad1b3cf9a3c5aad5b2a047e76b").unwrap(); - let start = 0; - let end = 16; - let result = buffer_slice(&buffer, start, end); - let converted = encode_hex(&result); - assert_eq!(&converted, "ba1bf00eed82b07ee65e574eb1f46043"); - } - - #[test] - fn test_convert_password_to_key() { - let key_salt_value = decode_hex("3aa973eec73c98c4710021730ef5b513").unwrap(); - let result = convert_password_to_key( - "password", - "SHA512", - &key_salt_value, - &100000, - &256, - &BLOCK_KEYS_KEY.to_vec(), - ); - let converted = encode_hex(&result); - assert_eq!( - &converted, - "8d5869311b1c1fdb59a1de6fe1e6f2ce7dccd4deb198a6dfb1f7fb55bc03487d" - ); - } -} diff --git a/src/helper/crypt/algo.rs b/src/helper/crypt/algo.rs new file mode 100644 index 00000000..4c8413f6 --- /dev/null +++ b/src/helper/crypt/algo.rs @@ -0,0 +1,372 @@ +//! Cryptographic operations for Excel document protection and encryption. +//! +//! This module implements the Office Open XML (OOXML) encryption standard for +//! Excel workbooks, providing functionality for: +//! +//! - AES-256-CBC encryption and decryption of package data +//! - XML-based encryption info generation +//! - Cryptographic utility functions +//! +//! # Architecture +//! +//! The module is structured around three main components: +//! +//! 1. Package encryption/decryption (`crypt_package`) +//! 2. Low-level AES operations (`crypt`) +//! 3. Encryption info XML generation (`build_encryption_info`) +//! +//! # Examples +//! +//! ``` +//! use crate::helper::crypt; +//! +//! // Encrypt package data +//! let encrypted = crypt::crypt_package(true, 16, &salt, &key, &input_data); +//! +//! // Perform raw AES-256-CBC encryption +//! let result = crypt::crypt(true, &key, &iv, &data).expect("Encryption failed"); +//! ``` +//! +//! # Implementation Details +//! +//! - Uses AES-256 in CBC mode for encryption/decryption +//! - Implements OOXML standard chunk-based processing +//! - Supports both encryption and decryption operations +//! - Handles padding and initialization vectors +//! - Generates standard-compliant encryption info XML +//! +//! # Dependencies +//! +//! - `aes`: AES block cipher implementation +//! - `cbc`: CBC mode encryption +//! - `base64`: Encoding of binary data +//! - `quick-xml`: XML generation +//! - `byteorder`: Byte order handling +//! +//! # Security Considerations +//! +//! - Uses cryptographically secure random values for salts +//! - Implements standard OOXML encryption specifications +//! - Properly handles encryption padding +//! - Validates key sizes and parameters + +use std::io; + +use aes::{ + Aes256, + cipher::{ + BlockDecryptMut, + BlockEncryptMut, + KeyIvInit, + block_padding::NoPadding, + }, +}; +use base64::{ + Engine as _, + engine::general_purpose::STANDARD, +}; +use byteorder::{ + ByteOrder, + LittleEndian, +}; +use cbc::{ + Decryptor, + Encryptor, +}; +use quick_xml::{ + Writer, + events::{ + BytesDecl, + Event, + }, +}; + +type Aes256CbcEnc = Encryptor; +type Aes256CbcDec = Decryptor; + +use super::{ + super::const_str::{ + CERTIFICATE_NS, + ENCRYPTION_NS, + PASSWORD_NS, + }, + constants, + key::create_iv, +}; +use crate::writer::driver::{ + write_end_tag, + write_new_line, + write_start_tag, +}; + +/// Encrypts or decrypts package data using AES-256 in CBC mode. +/// +/// # Parameters +/// - `encrypt`: Whether to encrypt (true) or decrypt (false) the data +/// - `block_size`: Size of encryption blocks in bytes +/// - `salt`: Salt value for IV generation +/// - `key`: Encryption/decryption key +/// - `input`: Data to encrypt/decrypt +/// +/// # Returns +/// A vector containing the encrypted/decrypted data. For encryption, includes +/// an 8-byte length prefix. For decryption, output is truncated to the original +/// length. +#[allow(clippy::cast_possible_truncation)] +pub(crate) fn crypt_package( + encrypt: bool, + block_size: usize, + salt: &[u8], + key: &[u8], + input: &[u8], +) -> Vec { + let mut output_chunks: Vec> = Vec::new(); + let offset = if encrypt { + 0 + } else { + constants::PACKAGE_OFFSET + }; + + // Process the package in chunks + let mut i: usize = 0; + let mut end = 0; + while end < input.len() { + let start = end; + end = (start + constants::PACKAGE_ENCRYPTION_CHUNK_SIZE).min(input.len()); + + // Get the next chunk + let mut input_chunk = input[start + offset..end + offset].to_vec(); + + // Pad the chunk if it is not a multiple of the block size + let remainder = input_chunk.len() % block_size; + if remainder > 0 { + input_chunk.extend(vec![0u8; block_size - remainder]); + } + + // Create the initialization vector (IV) for this chunk + let block_key_buffer = create_uint32_le_buffer(i as u32, None); + let iv = create_iv(salt, block_size, &block_key_buffer); + + // Encrypt or decrypt the chunk + let output_chunk = crypt(encrypt, key, &iv, &input_chunk).unwrap(); + output_chunks.push(output_chunk); + + i += 1; + } + + // Concatenate all output chunks + let mut output = output_chunks.concat(); + + if encrypt { + // Prepend the length of the package in the first 8 bytes + let input_len = input.len() as u32; + let length_buffer = create_uint32_le_buffer(input_len, Some(constants::PACKAGE_OFFSET)); + output = [length_buffer, output].concat(); + } else { + // Truncate the output to the size specified in the prefix + let length = LittleEndian::read_u32(&input[0..4]) as usize; + output.truncate(length); + } + + output +} + +/// Performs AES-256-CBC encryption or decryption on input data. +/// +/// # Arguments +/// * `encrypt` - If true, encrypts the data. If false, decrypts the data +/// * `key` - 256-bit encryption/decryption key +/// * `iv` - Initialization vector for CBC mode +/// * `input` - Data to be encrypted/decrypted +/// +/// # Returns +/// * `Ok(Vec)` - Encrypted/decrypted data +/// * `Err(String)` - Error message if operation fails +pub(crate) fn crypt(encrypt: bool, key: &[u8], iv: &[u8], input: &[u8]) -> Result, String> { + match key.len() * 8 { + 256 => { + if encrypt { + // Encrypt the input data + let cipher = Aes256CbcEnc::new_from_slices(key, iv) + .map_err(|e| format!("Error creating cipher: {e}"))?; + let mut buffer = input.to_vec(); + cipher + .encrypt_padded_mut::(&mut buffer, input.len()) + .map_err(|e| format!("Encryption error: {e}"))?; + Ok(buffer) + } else { + // Decrypt the input data + let cipher = Aes256CbcDec::new_from_slices(key, iv) + .map_err(|e| format!("Error creating cipher: {e}"))?; + let mut buffer = input.to_vec(); + cipher + .decrypt_padded_mut::(&mut buffer) + .map_err(|e| format!("Decryption error: {e}"))?; + Ok(buffer) + } + } + _ => Err("Key size not supported!".to_string()), + } +} + +/// Creates a little-endian byte buffer from a `u32` value. +/// +/// If `buffer_size` is provided and greater than 4, the buffer is padded with +/// zeros to match the specified size. +/// +/// # Parameters +/// - `value`: The `u32` value to convert. +/// - `buffer_size`: Optional desired size of the buffer. +/// +/// # Returns +/// A `Vec` containing the little-endian bytes of `value`, optionally padded +/// with zeros. +pub(crate) fn create_uint32_le_buffer(value: u32, buffer_size: Option) -> Vec { + let mut buffer = value.to_le_bytes().to_vec(); + if let Some(size) = buffer_size.filter(|&s| s > 4) { + buffer.resize(size, 0); + } + buffer +} + +/// Constructs the encryption info XML data for document protection. +/// +/// # Arguments +/// * `package_salt` - Salt value for package encryption +/// * `data_integrity_encrypted_hmac_key` - Encrypted HMAC key for data +/// integrity +/// * `data_integrity_encrypted_hmac_value` - Encrypted HMAC value for data +/// integrity +/// * `key_salt` - Salt value for key encryption +/// * `key_encrypted_verifier_hash_input` - Encrypted verifier hash input +/// * `key_encrypted_verifier_hash_value` - Encrypted verifier hash value +/// * `key_encrypted_key_value` - Encrypted key value +/// +/// # Returns +/// A vector of bytes containing the encryption info XML prefixed with the +/// standard header +pub(crate) fn build_encryption_info( + package_salt: &[u8], + data_integrity_encrypted_hmac_key: &[u8], + data_integrity_encrypted_hmac_value: &[u8], + key_salt: &[u8], + key_encrypted_verifier_hash_input: &[u8], + key_encrypted_verifier_hash_value: &[u8], + key_encrypted_key_value: &[u8], +) -> Vec { + let mut writer = Writer::new(io::Cursor::new(Vec::new())); + // XML header + writer + .write_event(Event::Decl(BytesDecl::new( + "1.0", + Some("UTF-8"), + Some("yes"), + ))) + .unwrap(); + write_new_line(&mut writer); + + // Start encryption element + write_start_tag( + &mut writer, + "encryption", + vec![ + ("xmlns", ENCRYPTION_NS).into(), + ("xmlns:p", PASSWORD_NS).into(), + ("xmlns:c", CERTIFICATE_NS).into(), + ], + false, + ); + + // keyData element + write_start_tag( + &mut writer, + "keyData", + vec![ + ("saltSize", &package_salt.len().to_string()).into(), + ("blockSize", &constants::PACKAGE_BLOCK_SIZE.to_string()).into(), + ("keyBits", &constants::PACKAGE_KEY_BITS.to_string()).into(), + ("hashSize", &constants::PACKAGE_HASH_SIZE.to_string()).into(), + ("cipherAlgorithm", constants::PACKAGE_CIPHER_ALGORITHM).into(), + ("cipherChaining", constants::PACKAGE_CIPHER_CHAINING).into(), + ("hashAlgorithm", constants::PACKAGE_HASH_ALGORITHM).into(), + ("saltValue", &STANDARD.encode(package_salt)).into(), + ], + true, + ); + + // dataIntegrity element + write_start_tag( + &mut writer, + "dataIntegrity", + vec![ + ( + "encryptedHmacKey", + &STANDARD.encode(data_integrity_encrypted_hmac_key), + ) + .into(), + ( + "encryptedHmacValue", + &STANDARD.encode(data_integrity_encrypted_hmac_value), + ) + .into(), + ], + true, + ); + + // keyEncryptors element + write_start_tag(&mut writer, "keyEncryptors", vec![], false); + + // keyEncryptor element + write_start_tag( + &mut writer, + "keyEncryptor", + vec![("uri", PASSWORD_NS).into()], + false, + ); + + // p:encryptedKey element + write_start_tag( + &mut writer, + "p:encryptedKey", + vec![ + ("spinCount", &constants::KEY_SPIN_COUNT.to_string()).into(), + ("saltSize", &key_salt.len().to_string()).into(), + ("blockSize", &constants::KEY_BLOCK_SIZE.to_string()).into(), + ("keyBits", &constants::KEY_BITLENGTH.to_string()).into(), + ("hashSize", &constants::KEY_HASH_SIZE.to_string()).into(), + ("cipherAlgorithm", constants::KEY_CIPHER_ALGORITHM).into(), + ("cipherChaining", constants::KEY_CIPHER_CHAINING).into(), + ("hashAlgorithm", constants::KEY_HASH_ALGORITHM).into(), + ("saltValue", &STANDARD.encode(key_salt)).into(), + ( + "encryptedVerifierHashInput", + &STANDARD.encode(key_encrypted_verifier_hash_input), + ) + .into(), + ( + "encryptedVerifierHashValue", + &STANDARD.encode(key_encrypted_verifier_hash_value), + ) + .into(), + ( + "encryptedKeyValue", + &STANDARD.encode(key_encrypted_key_value), + ) + .into(), + ], + true, + ); + + // Close tags + write_end_tag(&mut writer, "keyEncryptor"); + write_end_tag(&mut writer, "keyEncryptors"); + write_end_tag(&mut writer, "encryption"); + + let data = writer.into_inner().into_inner(); + + let mut result = Vec::with_capacity(constants::ENCRYPTION_INFO_PREFIX.len() + data.len()); + result.extend_from_slice(&constants::ENCRYPTION_INFO_PREFIX); + result.extend(data); + + result +} diff --git a/src/helper/crypt/constants.rs b/src/helper/crypt/constants.rs new file mode 100644 index 00000000..c4d12842 --- /dev/null +++ b/src/helper/crypt/constants.rs @@ -0,0 +1,67 @@ +//! Cryptographic constants and parameters for Excel document encryption. +//! +//! This module defines the standard constants used in the Office Open XML (OOXML) +//! encryption process, including: +//! +//! - Encryption format version identifiers +//! - Block keys for various encryption stages +//! - Package encryption parameters +//! - Key derivation constants +//! +//! # Implementation Details +//! +//! - Uses AES-256 for encryption +//! - Implements CBC mode for block chaining +//! - SHA-512 for hashing operations +//! - 100,000 iterations for key derivation +//! - 4096-byte chunks for package encryption +//! +//! # Standards Compliance +//! +//! These constants comply with the OOXML standard for document encryption +//! and are compatible with Microsoft Office encryption implementations. +//! +//! # Note +//! +//! These constants are essential for maintaining compatibility with the +//! Office Open XML encryption standard and should not be modified unless +//! the standard changes. + +/// Constants used in the encryption process +pub const ENCRYPTION_INFO_PREFIX: [u8; 8] = [0x04, 0x00, 0x04, 0x00, 0x40, 0x00, 0x00, 0x00]; // Version and reserved bytes + +pub const PACKAGE_ENCRYPTION_CHUNK_SIZE: usize = 4096; +pub const PACKAGE_OFFSET: usize = 8; // First 8 bytes are the size of the stream + +// Block keys used in various stages of encryption +pub const BLOCK_KEYS_DATA_INTEGRITY_HMAC_KEY: [u8; 8] = + [0x5f, 0xb2, 0xad, 0x01, 0x0c, 0xb9, 0xe1, 0xf6]; + +pub const BLOCK_KEYS_DATA_INTEGRITY_HMAC_VALUE: [u8; 8] = + [0xa0, 0x67, 0x7f, 0x02, 0xb2, 0x2c, 0x84, 0x33]; + +pub const BLOCK_KEYS_KEY: [u8; 8] = [0x14, 0x6e, 0x0b, 0xe7, 0xab, 0xac, 0xd0, 0xd6]; + +pub const BLOCK_VERIFIER_HASH_INPUT: [u8; 8] = [0xfe, 0xa7, 0xd2, 0x76, 0x3b, 0x4b, 0x9e, 0x79]; + +pub const BLOCK_VERIFIER_HASH_VALUE: [u8; 8] = [0xd7, 0xaa, 0x0f, 0x6d, 0x30, 0x61, 0x34, 0x4e]; + +// Package parameters +pub const PACKAGE_BLOCK_SIZE: usize = 16; +pub const PACKAGE_CIPHER_ALGORITHM: &str = "AES"; +pub const PACKAGE_CIPHER_CHAINING: &str = "ChainingModeCBC"; +pub const PACKAGE_HASH_ALGORITHM: &str = "SHA512"; +pub const PACKAGE_HASH_SIZE: usize = 64; +pub const PACKAGE_KEY_BITS: usize = PACKAGE_KEY_LENGTH * 8; +pub const PACKAGE_KEY_LENGTH: usize = 32; + +// Key parameters +pub const KEY_BITLENGTH: usize = 256; +pub const KEY_BLOCK_SIZE: usize = 16; +pub const KEY_CIPHER_ALGORITHM: &str = "AES"; +pub const KEY_CIPHER_CHAINING: &str = "ChainingModeCBC"; +pub const KEY_HASH_ALGORITHM: &str = "SHA-512"; +pub const KEY_HASH_SIZE: usize = 64; +pub const KEY_SPIN_COUNT: usize = 100_000; + +// Ths file is ignored by rustfmt. diff --git a/src/helper/crypt/key.rs b/src/helper/crypt/key.rs new file mode 100644 index 00000000..08c0075e --- /dev/null +++ b/src/helper/crypt/key.rs @@ -0,0 +1,199 @@ +//! Password-based key derivation and cryptographic hash functions for Excel +//! protection. +//! +//! This module implements specialized cryptographic operations following the +//! Office Open XML standard for document protection, including: +//! +//! - HMAC-SHA512 generation +//! - Initialization Vector (IV) creation +//! - Password-to-key derivation +//! - Password hashing with salt and iteration count +//! +//! # Key Features +//! +//! - UTF-16LE password encoding for Microsoft Office compatibility +//! - Configurable key strengthening through iteration counts +//! - Salt-based key derivation +//! - Flexible IV generation with block size adjustment +//! +//! # Examples +//! +//! Generate an HMAC: +//! ``` +//! use crate::crypto::hmac; +//! +//! let key = b"secret_key"; +//! let data = [b"data1", b"data2"]; +//! let mac = hmac(key, &data); +//! ``` +//! +//! Create a key from password: +//! ``` +//! use crate::crypto::convert_password_to_key; +//! +//! let password = "MyPassword123"; +//! let salt = vec![1, 2, 3, 4]; +//! let key = convert_password_to_key( +//! password, &salt, 100_000, // spin count +//! 256, // key bits +//! &[0u8; 16], // block key +//! ); +//! ``` +//! +//! # Implementation Details +//! +//! - Uses SHA-512 for all hashing operations +//! - HMAC implementation via the `hmac` crate +//! - Automatic handling of key/IV size requirements +//! - Consistent password encoding for Office compatibility +//! +//! # Security Considerations +//! +//! - Implements industry-standard key derivation practices +//! - Uses cryptographically secure hash functions +//! - Supports configurable iteration counts for key strengthening +//! - Properly handles salt integration + +use std::cmp::Ordering; + +use hmac::{ + Hmac, + Mac, +}; +use sha2::{ + Digest, + Sha512, +}; + +use super::utils::hash_concatenated; + +/// Calculates an HMAC using SHA-512 over concatenated input buffers. +/// +/// # Arguments +/// * `key` - The key used for HMAC calculation +/// * `buffers` - Slice of byte slices to be concatenated and hashed +/// +/// # Returns +/// A vector containing the calculated HMAC bytes +pub(crate) fn hmac(key: &[u8], buffers: &[&[u8]]) -> Vec { + let mut mac = Hmac::::new_from_slice(key).unwrap(); + for buffer in buffers { + mac.update(buffer); + } + mac.finalize().into_bytes().to_vec() +} + +/// Creates an IV by hashing the salt value and block key together. +/// The resulting hash is adjusted to match the specified block size +/// by either padding with zeros or truncating. +/// +/// # Arguments +/// * `salt_value` - Salt value to use in hash +/// * `block_size` - Target size for the IV in bytes +/// * `block_key` - Block key to use in hash +/// +/// # Returns +/// A vector containing the IV adjusted to the block size +pub(crate) fn create_iv(salt_value: &[u8], block_size: usize, block_key: &[u8]) -> Vec { + // Hash the salt value and block key together + let mut iv = hash_concatenated(&[salt_value, block_key]); + + // Adjust the IV length to match the block size + match iv.len().cmp(&block_size) { + Ordering::Less => { + // Pad with zeros if IV is shorter than block size + iv.resize(block_size, 0); + } + Ordering::Greater => { + // Truncate if IV is longer than block size + iv.truncate(block_size); + } + Ordering::Equal => {} + } + + iv +} + +/// Generates a cryptographic key from a password using SHA-512 hashing. +/// +/// # Arguments +/// * `password` - Password string to convert +/// * `salt` - Salt bytes for key derivation +/// * `spin_count` - Number of iterations for key strengthening +/// * `key_bits` - Desired key length in bits +/// * `block_key` - Additional key material for final hash +/// +/// # Returns +/// A vector containing the derived key bytes, truncated or padded to match +/// `key_bits` length +#[allow(clippy::cast_possible_truncation)] +pub(crate) fn convert_password_to_key( + password: &str, + salt: &[u8], + spin_count: usize, + key_bits: usize, + block_key: &[u8], +) -> Vec { + // Convert password to UTF-16LE bytes + let password_bytes: Vec = password.encode_utf16().flat_map(u16::to_le_bytes).collect(); + + let mut hasher = Sha512::new(); + + hasher.update(salt); + hasher.update(&password_bytes); + + let mut key = hasher.finalize(); + + // Iterate spin_count times + for i in 0..spin_count { + let i_bytes = (i as u32).to_le_bytes(); + let mut hasher = Sha512::new(); + hasher.update(i_bytes); + hasher.update(key); + key = hasher.finalize(); + } + + let mut hasher = Sha512::new(); + hasher.update(key); + hasher.update(block_key); + let mut key = hasher.finalize().to_vec(); + + // Truncate or pad the key to the desired length + let key_bytes = key_bits / 8; + match key.len().cmp(&key_bytes) { + Ordering::Less => { + // Pad with zeros + key.resize(key_bytes, 0); + key + } + Ordering::Greater => key[..key_bytes].to_vec(), + Ordering::Equal => key, + } +} + +/// Generates a cryptographic hash of a password using SHA-512. +/// +/// # Arguments +/// * `password` - The password string to hash +/// * `salt` - The salt bytes to use in hashing +/// * `spin_count` - Number of iterations for the hash function +/// +/// # Returns +/// A vector containing the final hash value +#[allow(clippy::cast_possible_truncation)] +pub(crate) fn convert_password_to_hash(password: &str, salt: &[u8], spin_count: usize) -> Vec { + // Convert password to UTF-16LE bytes + let password_bytes: Vec = password.encode_utf16().flat_map(u16::to_le_bytes).collect(); + + let mut hasher = Sha512::new(); + hasher.update(salt); + hasher.update(password_bytes); + + // Iterate spin_count times + for i in 0..spin_count { + let i_bytes = (i as u32).to_le_bytes(); + hasher.update(i_bytes); + } + + hasher.finalize().to_vec() +} diff --git a/src/helper/crypt/mod.rs b/src/helper/crypt/mod.rs new file mode 100644 index 00000000..71f589e7 --- /dev/null +++ b/src/helper/crypt/mod.rs @@ -0,0 +1,557 @@ +//! Cryptographic utilities for Excel workbook and worksheet protection. +//! +//! This module provides functionality for encrypting and managing protection +//! settings in Excel workbooks and worksheets. It includes methods for: +//! - Password-based encryption +//! - Hash generation +//! - Salt management +//! - Protection settings configuration +//! +//! The encryption process follows Microsoft's Office Open XML standards for +//! document protection, using standardized algorithms and key derivation +//! functions. +//! +//! # Examples +//! +//! ``` +//! use crate::{ +//! helper::crypt, +//! structs::SheetProtection, +//! }; +//! +//! let mut protection = SheetProtection::new(); +//! crypt::encrypt_sheet_protection("mypassword", &mut protection); +//! ``` +//! +//! # Security +//! +//! The module implements industry-standard cryptographic practices: +//! - Random salt generation for each encryption +//! - Configurable key spin count for key derivation +//! - Base64 encoding for binary data storage +//! - Secure password hashing + +use std::{ + io::Write, + path::Path, +}; + +use rand::Rng; + +use crate::structs::{ + SheetProtection, + WorkbookProtection, +}; + +pub(crate) mod algo; +pub(crate) mod constants; +pub(crate) mod key; +pub(crate) mod utils; + +use base64::{ + Engine as _, + engine::general_purpose::STANDARD, +}; +use utils::generate_random_bytes; + +/// Encrypts the sheet protection using the provided password. +/// +/// Updates the `sheet_protection` object with the algorithm name, salt value, +/// spin count, and hash value. +/// +/// # Parameters +/// +/// - `password`: The password to use for encryption. +/// - `sheet_protection`: The `SheetProtection` object to update with encryption +/// details. +#[allow(clippy::cast_possible_truncation)] +pub fn encrypt_sheet_protection(password: &str, sheet_protection: &mut SheetProtection) { + generate_random_bytes!(salt, 16); + + // Convert the password into a hash + let key = key::convert_password_to_hash(password, &salt, constants::KEY_SPIN_COUNT); + + // Encode the salt and hash value in base64 + let salt_value_str = STANDARD.encode(salt); + let hash_value_str = STANDARD.encode(key); + + // Update the sheet_protection object + sheet_protection.set_algorithm_name(constants::KEY_HASH_ALGORITHM); + sheet_protection.set_salt_value(salt_value_str); + sheet_protection.set_spin_count(constants::KEY_SPIN_COUNT as u32); + sheet_protection.set_hash_value(hash_value_str); + sheet_protection.remove_password_raw(); +} + +/// Encrypts the workbook protection using the provided password. +/// +/// Updates the `workbook_protection` object with the algorithm name, salt +/// value, spin count, and hash value. +/// +/// # Parameters +/// +/// - `password`: The password to use for encryption. +/// - `workbook_protection`: The `WorkbookProtection` object to update with +/// encryption details. +#[allow(clippy::cast_possible_truncation)] +pub fn encrypt_workbook_protection(password: &str, workbook_protection: &mut WorkbookProtection) { + generate_random_bytes!(salt, 16); + + // Convert the password into a hash + let key = key::convert_password_to_hash(password, &salt, constants::KEY_SPIN_COUNT); + + // Encode the salt and hash value in base64 + let salt_value_str = STANDARD.encode(salt); + let hash_value_str = STANDARD.encode(key); + + // Update the workbook_protection object + workbook_protection.set_workbook_algorithm_name(constants::KEY_HASH_ALGORITHM); + workbook_protection.set_workbook_salt_value(salt_value_str); + workbook_protection.set_workbook_spin_count(constants::KEY_SPIN_COUNT as u32); + workbook_protection.set_workbook_hash_value(hash_value_str); + workbook_protection.remove_workbook_password_raw(); +} + +/// Encrypts the revisions protection using the provided password. +/// +/// This function generates a random salt, derives a key from the password using +/// SHA-512, and updates the workbook protection settings with the encrypted +/// values. +/// +/// # Arguments +/// +/// * `password` - The password string to use for encryption +/// * `workbook_protection` - Mutable reference to the `WorkbookProtection` +/// object to update +#[allow(clippy::cast_possible_truncation)] +pub fn encrypt_revisions_protection(password: &str, workbook_protection: &mut WorkbookProtection) { + generate_random_bytes!(salt, 16); + + // Convert the password into a hash + let key = key::convert_password_to_hash(password, &salt, constants::KEY_SPIN_COUNT); + + // Encode the salt and hash value in base64 + let salt_value_str = STANDARD.encode(salt); + let hash_value_str = STANDARD.encode(key); + + // Update the workbook_protection object + workbook_protection.set_revisions_algorithm_name(constants::KEY_HASH_ALGORITHM); + workbook_protection.set_revisions_salt_value(salt_value_str); + workbook_protection.set_revisions_spin_count(constants::KEY_SPIN_COUNT as u32); + workbook_protection.set_revisions_hash_value(hash_value_str); + workbook_protection.remove_revisions_password_raw(); +} + +/// Encrypts the given data and writes it to a specified file. +/// +/// This function performs the following steps: +/// 1. Generates random bytes for various cryptographic keys and salts. +/// 2. Encrypts the data using a specified package cipher algorithm (AES). +/// 3. Generates and encrypts an HMAC key and its corresponding value for data +/// integrity. +/// 4. Converts the provided password into a key using a key derivation +/// function. +/// 5. Encrypts the derived key and generates a verifier hash input and value, +/// both of which are also encrypted. +/// 6. Constructs an XML structure containing encryption information. +/// 7. Creates a compound file and writes the encrypted data and metadata to it. +/// +/// # Parameters +/// +/// - `filepath`: A reference to the path where the encrypted data will be +/// saved. This can be any type that implements the `AsRef` trait. +/// - `data`: A byte slice containing the data to be encrypted. +/// - `password`: A string slice representing the password used for key +/// derivation. +/// +/// # Errors +/// +/// This function may return errors related to file I/O or cryptographic +/// operations. Ensure to handle these errors appropriately in your application. +/// +/// # Panics +/// +/// This function may panic if: +/// - The underlying file operations fail (e.g., if the file cannot be created +/// or written to). +/// - Any cryptographic operation fails, such as key generation or encryption, +/// due to invalid parameters or internal errors. +/// +/// # Example +/// +/// ``` +/// let data = b"Sensitive data to encrypt"; +/// let password = "securepassword"; +/// let filepath = "encrypted_data.bin"; +/// +/// encrypt(&filepath, data, password); +/// ``` +/// +/// # Note +/// +/// The encryption process involves multiple cryptographic operations, including +/// key generation, HMAC creation, and data encryption. Ensure that the password +/// used is strong and kept secure. +pub fn encrypt>(filepath: &P, data: &[u8], password: &str) { + generate_random_bytes!(hmac_key, 64); + generate_random_bytes!(key_salt, 16); + generate_random_bytes!(package_key, 32); + generate_random_bytes!(package_salt, 16); + generate_random_bytes!(verifier_hash_input, 16); + + // Encrypt the package + let encrypted_package = algo::crypt_package( + true, + constants::PACKAGE_BLOCK_SIZE, + &package_salt, + &package_key, + data, + ); + + // Generate HMAC key and encrypt it + let hmac_key_iv = key::create_iv( + &package_salt, + constants::PACKAGE_BLOCK_SIZE, + &constants::BLOCK_KEYS_DATA_INTEGRITY_HMAC_KEY, + ); + let encrypted_hmac_key = algo::crypt(true, &package_key, &hmac_key_iv, &hmac_key).unwrap(); + + // Generate HMAC value and encrypt it + let hmac_value = key::hmac(&hmac_key, &[&encrypted_package]); + let hmac_value_iv = key::create_iv( + &package_salt, + constants::PACKAGE_BLOCK_SIZE, + &constants::BLOCK_KEYS_DATA_INTEGRITY_HMAC_VALUE, + ); + let encrypted_hmac_value = + algo::crypt(true, &package_key, &hmac_value_iv, &hmac_value).unwrap(); + + // Convert the password to a key + let key = key::convert_password_to_key( + password, + &key_salt, + constants::KEY_SPIN_COUNT, + constants::KEY_BITLENGTH, + &constants::BLOCK_KEYS_KEY, + ); + let encrypted_key_value = algo::crypt(true, &key, &key_salt, &package_key).unwrap(); + + // Generate verifier hash input and encrypt it + let verifier_hash_input_key = key::convert_password_to_key( + password, + &key_salt, + constants::KEY_SPIN_COUNT, + constants::KEY_BITLENGTH, + &constants::BLOCK_VERIFIER_HASH_INPUT, + ); + let encrypted_verifier_hash_input = algo::crypt( + true, + &verifier_hash_input_key, + &key_salt, + &verifier_hash_input, + ) + .unwrap(); + + // Generate verifier hash value and encrypt it + let verifier_hash_value = utils::hash_concatenated(&[&verifier_hash_input]); + let verifier_hash_value_key = key::convert_password_to_key( + password, + &key_salt, + constants::KEY_SPIN_COUNT, + constants::KEY_BITLENGTH, + &constants::BLOCK_VERIFIER_HASH_VALUE, + ); + let encrypted_verifier_hash_value = algo::crypt( + true, + &verifier_hash_value_key, + &key_salt, + &verifier_hash_value, + ) + .unwrap(); + + // Build the encryption info XML data + let encryption_info_buffer = algo::build_encryption_info( + &package_salt, + &encrypted_hmac_key, + &encrypted_hmac_value, + &key_salt, + &encrypted_verifier_hash_input, + &encrypted_verifier_hash_value, + &encrypted_key_value, + ); + + // Create compound file and write streams + let mut comp = cfb::create(filepath).unwrap(); + { + let mut stream_info = comp.create_stream("EncryptionInfo").unwrap(); + stream_info.write_all(&encryption_info_buffer).unwrap(); + } + { + let mut stream_package = comp.create_stream("EncryptedPackage").unwrap(); + stream_package.write_all(&encrypted_package).unwrap(); + } +} + +#[allow(unused_imports)] +#[cfg(test)] +mod tests { + use std::fs; + + use hex_literal::hex; + use sha2::{ + Digest, + Sha256, + }; + + use super::*; + use crate::helper::utils::{ + assert_sha256, + print_hex, + print_sha256_hex, + }; + + #[test] + fn test_encrypt_package() { + let data = fs::read("./tests/test_files/aaa.xlsx").unwrap(); + + // Package parameters + let package_key = hex!("cdf9defae2480933c503350e16334453d1cb8348bb2fea585db7f9e1f78fe9bf"); + let package_salt = hex!("4c251b321d85cecfcb6d952ba6d81846"); + + // Encrypted package + let encrypted_package = algo::crypt_package( + true, + constants::PACKAGE_BLOCK_SIZE, + &package_salt, + &package_key, + &data, + ); + + assert_sha256!( + &encrypted_package, + "c2f7aa6ef36f5389aee63255887e103d7e9d9388c6dcaaa821fb62119ebdd697" + ); + } + + #[test] + fn test_encrypt_hmac_key() { + // Package parameters + let package_key = hex!("cdf9defae2480933c503350e16334453d1cb8348bb2fea585db7f9e1f78fe9bf"); + let package_salt = hex!("4c251b321d85cecfcb6d952ba6d81846"); + + // HMAC key + let hmac_key = hex!( + "4c6e4db6d9a60e5d41c3ca639a682aaa71da7437202fe92ec5d814bd1e9e4e6a" + "831aee889eae3bc18bc1bebedae1f73393fddfffd0a0b6c557485fefcdb5e98b" + ); + + let hmac_key_iv = key::create_iv( + &package_salt, + constants::PACKAGE_BLOCK_SIZE, + &constants::BLOCK_KEYS_DATA_INTEGRITY_HMAC_KEY, + ); + assert_eq!(hmac_key_iv, hex!("ba1bf00eed82b07ee65e574eb1f46043")); + + let encrypted_hmac_key = algo::crypt(true, &package_key, &hmac_key_iv, &hmac_key).unwrap(); + assert_eq!( + encrypted_hmac_key, + hex!( + "b32b1cdc4ac1af244377c1eb57efd31a819f555a7204adcc0cfe364b394bbdb0 + 86a8daef4f4c512d52e3db6a54b1d45e1dd1dbfa3ddacc29fe35449ba5225dc7" + ) + ); + } + + #[test] + fn test_encrypt_hmac_value() { + let hmac_key = hex!( + "4c6e4db6d9a60e5d41c3ca639a682aaa71da7437202fe92ec5d814bd1e9e4e6a" + "831aee889eae3bc18bc1bebedae1f73393fddfffd0a0b6c557485fefcdb5e98b" + ); + + // Use the data specific from "test_encrypt" + let data = fs::read("./tests/test_files/aaa.xlsx").unwrap(); + + let encrypted_package = algo::crypt_package( + true, + constants::PACKAGE_BLOCK_SIZE, + &hex!("4c251b321d85cecfcb6d952ba6d81846"), // package_salt + &hex!("cdf9defae2480933c503350e16334453d1cb8348bb2fea585db7f9e1f78fe9bf"), /* package_key */ + &data, + ); + + let hmac_value = key::hmac(&hmac_key, &[&encrypted_package]); + assert_eq!( + hmac_value, + hex!( + "48ed7b8718e55f7f6e19e1cae6a447afacff5e5e3fe92ac836908e6cdeeb68a2" + "5fa1cbaaa4d7f7c0acabc2f7f22ad87bf11eaf74169fb6e6f78ab033e038928c" + ) + ); + + let hmac_value_iv = key::create_iv( + &hex!("4c251b321d85cecfcb6d952ba6d81846"), + constants::PACKAGE_BLOCK_SIZE, + &constants::BLOCK_KEYS_DATA_INTEGRITY_HMAC_VALUE, + ); + assert_eq!(hmac_value_iv, hex!("088385b871292e7ed8414f173c5b6622")); + + let encrypted_hmac_value = algo::crypt( + true, + &hex!("cdf9defae2480933c503350e16334453d1cb8348bb2fea585db7f9e1f78fe9bf"), + &hmac_value_iv, + &hmac_value, + ) + .unwrap(); + assert_eq!( + encrypted_hmac_value, + hex!( + "f75c7f3c44fadf9b4bbf2ff693586710c52e043d8db69e3e538be5f10d36f86d" + "24dd5f4b9f71a2ce928abbbfe46e791a6c683703bcb30d5214997e60bbd547f6" + ) + ); + } + + #[test] + fn test_convert_password_to_key() { + let key_salt = hex!("3aa973eec73c98c4710021730ef5b513"); + let password = "password"; + + let key = key::convert_password_to_key( + password, + &key_salt, + constants::KEY_SPIN_COUNT, + constants::KEY_BITLENGTH, + &constants::BLOCK_KEYS_KEY, + ); + assert_eq!( + key, + hex!("8d5869311b1c1fdb59a1de6fe1e6f2ce7dccd4deb198a6dfb1f7fb55bc03487d") + ); + + let encrypted_key_value = algo::crypt( + true, + &key, + &key_salt, + &hex!("cdf9defae2480933c503350e16334453d1cb8348bb2fea585db7f9e1f78fe9bf"), + ) + .unwrap(); + assert_eq!( + encrypted_key_value, + hex!("5017ddc6146e56dfbf76734b3e99b80f36a4c9a2e9eb21fe77695f73850cc452") + ); + } + + #[test] + fn test_encrypt_verifier_hash() { + let key_salt = hex!("3aa973eec73c98c4710021730ef5b513"); + let password = "password"; + + // Verifier hash input + let verifier_hash_input = hex!("8f54777cba87efa55ea2db8399873815"); + let verifier_hash_input_key = key::convert_password_to_key( + password, + &key_salt, + constants::KEY_SPIN_COUNT, + constants::KEY_BITLENGTH, + &constants::BLOCK_VERIFIER_HASH_INPUT, + ); + assert_eq!( + verifier_hash_input_key, + hex!("44e4b664c512b08e7577aa3fc7e11ad603e0877a476931fad5aa79e203304aff") + ); + + let encrypted_verifier_hash_input = algo::crypt( + true, + &verifier_hash_input_key, + &key_salt, + &verifier_hash_input, + ) + .unwrap(); + assert_eq!( + encrypted_verifier_hash_input, + hex!("2fb9eea58e227ffa549449e941f1199e") + ); + + // Verifier hash value + let verifier_hash_value = utils::hash_concatenated(&[&verifier_hash_input]); + assert_eq!( + verifier_hash_value, + hex!( + "920b1de74f38d9cb3ccb3394119ed37e958404fdc47560b1bf647d3c49c22549" + "625fe4a0bd36798bd68a0d98ae64f6ab64a330c9890c62bb740aa492c226ae1f" + ) + ); + + let verifier_hash_value_key = key::convert_password_to_key( + password, + &key_salt, + constants::KEY_SPIN_COUNT, + constants::KEY_BITLENGTH, + &constants::BLOCK_VERIFIER_HASH_VALUE, + ); + assert_eq!( + verifier_hash_value_key, + hex!("d5515a6062e3e99551b80b92db1fe646483884cdb63e1e7595a9f2cca7532884") + ); + + let encrypted_verifier_hash_value = algo::crypt( + true, + &verifier_hash_value_key, + &key_salt, + &verifier_hash_value, + ) + .unwrap(); + assert_eq!( + encrypted_verifier_hash_value, + hex!( + "0d9c888111b40b630b739c95a5f5b6be67c8f96acdd1bee185bd808b507f6527" + "60a2e77f63a6ad0c46f985f2bb8dab4fcf9b86d6a40d9c21299bb4ddf788b250" + ) + ); + } + + #[test] + fn test_build_encryption_info() { + let encryption_info = algo::build_encryption_info( + &hex!("4c251b321d85cecfcb6d952ba6d81846"), + &hex!( + "b32b1cdc4ac1af244377c1eb57efd31a819f555a7204adcc0cfe364b394bbdb0" + "86a8daef4f4c512d52e3db6a54b1d45e1dd1dbfa3ddacc29fe35449ba5225dc7" + ), + &hex!( + "f75c7f3c44fadf9b4bbf2ff693586710c52e043d8db69e3e538be5f10d36f86d" + "24dd5f4b9f71a2ce928abbbfe46e791a6c683703bcb30d5214997e60bbd547f6" + ), + &hex!("3aa973eec73c98c4710021730ef5b513"), + &hex!("2fb9eea58e227ffa549449e941f1199e"), + &hex!( + "0d9c888111b40b630b739c95a5f5b6be67c8f96acdd1bee185bd808b507f6527 + 60a2e77f63a6ad0c46f985f2bb8dab4fcf9b86d6a40d9c21299bb4ddf788b250" + ), + &hex!("5017ddc6146e56dfbf76734b3e99b80f36a4c9a2e9eb21fe77695f73850cc452"), + ); + + assert_sha256!( + encryption_info, + "31f0b53f3d92aa053607641946534b96488e9df8abe64268cc1f337b5e4de8b8" + ); + } + + #[test] + fn test_hash() { + let package_salt = hex!("4c251b321d85cecfcb6d952ba6d81846"); + let result = utils::hash_concatenated(&[ + &package_salt, + &constants::BLOCK_KEYS_DATA_INTEGRITY_HMAC_KEY, + ]); + + assert_eq!( + result, + hex!( + "ba1bf00eed82b07ee65e574eb1f460435d2a1405e81904fd01d5ed5adf43fdcf + d8e9aeebad0c08065e0db20cdc8e4552744b61ad1b3cf9a3c5aad5b2a047e76b" + ) + ); + } +} diff --git a/src/helper/crypt/utils.rs b/src/helper/crypt/utils.rs new file mode 100644 index 00000000..824bad1d --- /dev/null +++ b/src/helper/crypt/utils.rs @@ -0,0 +1,153 @@ +//! Cryptographic utility functions and macros for secure operations. +//! +//! This module provides essential cryptographic utilities including: +//! - Random byte generation +//! - Buffer concatenation +//! - SHA-512 hashing +//! +//! # Features +//! +//! - Secure random number generation using the `rand` crate +//! - Efficient buffer concatenation with pre-allocation +//! - SHA-512 hash computation using the `sha2` crate +//! +//! # Examples +//! +//! Generate random bytes: +//! ``` +//! use crate::helper::crypto::generate_random_bytes; +//! +//! generate_random_bytes!(salt, 16); +//! // `salt` now contains 16 cryptographically secure random bytes +//! ``` +//! +//! Concatenate buffers and compute hash: +//! ``` +//! use crate::helper::crypto::{ +//! buffer_concat, +//! hash_concatenated, +//! }; +//! +//! let data1 = b"password"; +//! let data2 = b"salt"; +//! +//! // Concatenate buffers +//! let combined = buffer_concat(&[data1, data2]); +//! +//! // Compute SHA-512 hash +//! let hash = hash_concatenated(&[data1, data2]); +//! ``` +//! +//! # Implementation Details +//! +//! The module uses: +//! - Thread-local random number generator for secure byte generation +//! - Optimized buffer concatenation with pre-allocated capacity +//! - SHA-512 implementation from the `sha2` crate +//! +//! # Security Considerations +//! +//! - Uses cryptographically secure random number generation +//! - Implements standard SHA-512 hashing +//! - Ensures efficient memory management for large buffers +//! +//! # Note +//! +//! This module is intended for internal use within the crate, as indicated +//! by the `pub(crate)` visibility modifiers. + +use sha2::{ + Digest, + Sha512, +}; + +/// A macro that generates an array of random bytes. +/// +/// This macro initializes a variable with the specified name and fills it +/// with random bytes of the given size using the `rand` crate. +/// +/// # Parameters +/// +/// - `$var_name`: The name of the variable to hold the generated random bytes. +/// - `$size`: The size of the byte array to be generated. +/// +/// # Example +/// +/// ``` +/// generate_random_bytes!(random_bytes, 16); +/// // `random_bytes` now contains 16 random bytes. +/// ``` +macro_rules! generate_random_bytes { + ($var_name:ident, $size:expr) => { + let mut $var_name = [0u8; $size]; + rand::thread_rng().fill(&mut $var_name); + }; +} + +/// Concatenates multiple byte slices into a single `Vec`. +/// +/// This function takes a slice of byte slices and computes the total length +/// of the resulting vector. It preallocates the vector with the total length +/// and extends it with each input buffer. +/// +/// # Parameters +/// +/// - `buffers`: A slice of byte slices to be concatenated. +/// +/// # Returns +/// +/// A `Vec` containing all the concatenated byte slices. +/// +/// # Example +/// +/// ``` +/// let buffer1 = b"Hello, "; +/// let buffer2 = b"world!"; +/// let result = buffer_concat(&[buffer1, buffer2]); +/// assert_eq!(result, b"Hello, world!"); +/// ``` +pub(crate) fn buffer_concat(buffers: &[&[u8]]) -> Vec { + // Calculate the total length of the resulting vector. + let total_length = buffers.iter().map(|buffer| buffer.len()).sum(); + // Preallocate the vector with the total length. + let mut result: Vec = Vec::with_capacity(total_length); + // Extend the vector with each buffer. + for buffer in buffers { + result.extend_from_slice(buffer); + } + result +} + +/// Computes the SHA-512 hash of concatenated byte slices. +/// +/// This function takes a slice of byte slices, concatenates them, and computes +/// their SHA-512 hash. The resulting hash is returned as a vector of bytes. +/// +/// # Parameters +/// +/// - `buffers`: A slice of byte slices to be hashed. +/// +/// # Returns +/// +/// A `Vec` containing the SHA-512 hash of the concatenated input buffers. +/// +/// # Example +/// +/// ``` +/// let buffer1 = b"Hello, "; +/// let buffer2 = b"world!"; +/// let hash = hash_concatenated(&[buffer1, buffer2]); +/// // `hash` now contains the SHA-512 hash of "Hello, world!". +/// ``` +pub(crate) fn hash_concatenated(buffers: &[&[u8]]) -> Vec { + let mut hasher = Sha512::new(); + + for buffer in buffers { + hasher.update(buffer); + } + + hasher.finalize().to_vec() +} + +/// Re-exports the `generate_random_bytes` macro for use in other modules. +pub(crate) use generate_random_bytes; diff --git a/src/helper/date.rs b/src/helper/date.rs index 049f4767..27c41b94 100644 --- a/src/helper/date.rs +++ b/src/helper/date.rs @@ -1,23 +1,74 @@ -use chrono::{Duration, NaiveDateTime}; +use chrono::{ + Duration, + NaiveDateTime, +}; +use num_traits::cast; pub const CALENDAR_WINDOWS_1900: &str = "1900"; pub const CALENDAR_MAC_1904: &str = "1904"; +pub const DEFAULT_TIMEZONE: &str = "UTC"; -pub fn excel_to_date_time_object( - excel_timestamp: &f64, - time_zone: Option, -) -> NaiveDateTime { +/// Converts an Excel timestamp to a `NaiveDateTime` object. +/// +/// This function takes an Excel timestamp, which is a numeric representation of +/// a date and time, and converts it into a `NaiveDateTime` object. The integer +/// part of the timestamp represents the number of days since a base date, while +/// the fractional part represents the time of day. +/// +/// # Parameters +/// +/// - `excel_timestamp: f64` The Excel timestamp to be converted. If the value +/// is less than 1, it is treated as a Unix timestamp (seconds since January +/// 1, 1970). If the value is greater than or equal to 1, it is treated as an +/// Excel date. +/// +/// - `time_zone: Option` An optional string representing the desired +/// time zone. If `None`, the function will use a default time zone obtained +/// from the `get_default_timezone()` function. +/// +/// # Returns +/// +/// Returns a `NaiveDateTime` object representing the converted date and time. +/// This object does not contain any timezone information. +/// +/// # Behavior +/// +/// - If `excel_timestamp` is less than 1, it is interpreted as a Unix +/// timestamp, using January 1, 1970, as the base date. +/// - If `excel_timestamp` is between 1 and 60, the function accounts for the +/// 1900 leap year bug in Excel by using December 31, 1899, as the base date. +/// - For `excel_timestamp` values of 60 or greater, it uses December 30, 1899, +/// as the base date. +/// +/// # Example +/// +/// ```rust +/// let timestamp = 44204.5; // Represents 2021-01-01 12:00:00 +/// let date_time = excel_to_date_time_object(timestamp, None); +/// assert_eq!(date_time.year(), 2021); +/// assert_eq!(date_time.month(), 1); +/// assert_eq!(date_time.day(), 1); +/// assert_eq!(date_time.hour(), 12); +/// assert_eq!(date_time.minute(), 0); +/// ``` +/// +/// # Panics +/// +/// This function will panic if the parsing of the base date fails. Ensure that +/// the input timestamp is valid and within the expected range. +#[must_use] +pub fn excel_to_date_time_object(excel_timestamp: f64, time_zone: Option) -> NaiveDateTime { let _time_zone = match time_zone { Some(v) => v, - None => get_default_timezone(), + None => DEFAULT_TIMEZONE.to_owned(), }; - let mut base_date = if excel_timestamp < &1f64 { + let base_date = if excel_timestamp < 1f64 { // Unix timestamp base date NaiveDateTime::parse_from_str("1970-01-01 00:00:00", "%Y-%m-%d %T").unwrap() } else { // Allow adjustment for 1900 Leap Year in MS Excel - if excel_timestamp < &60f64 { + if excel_timestamp < 60f64 { NaiveDateTime::parse_from_str("1899-12-31 00:00:00", "%Y-%m-%d %T").unwrap() } else { NaiveDateTime::parse_from_str("1899-12-30 00:00:00", "%Y-%m-%d %T").unwrap() @@ -33,18 +84,51 @@ pub fn excel_to_date_time_object( let seconds = (part_day * 60f64).round(); base_date - + Duration::days(days as i64) - + Duration::hours(hours as i64) - + Duration::minutes(minutes as i64) - + Duration::seconds(seconds as i64) -} - -#[inline] -fn get_default_timezone() -> String { - String::from("UTC") + + Duration::days(cast(days).unwrap()) + + Duration::hours(cast(hours).unwrap()) + + Duration::minutes(cast(minutes).unwrap()) + + Duration::seconds(cast(seconds).unwrap()) } +/// Converts a date and time to an Excel timestamp using the Windows 1900 date +/// system. +/// +/// This function takes individual components of a date and time (year, month, +/// day, hours, minutes, and seconds) and converts them into an Excel timestamp. +/// The conversion is based on the Windows 1900 date system, which accounts for +/// the 1900 leap year bug. +/// +/// # Parameters +/// +/// - `year: i32` The year component of the date. +/// +/// - `month: i32` The month component of the date (1-12). +/// +/// - `day: i32` The day component of the date (1-31). +/// +/// - `hours: i32` The hour component of the time (0-23). +/// +/// - `minutes: i32` The minute component of the time (0-59). +/// +/// - `seconds: i32` The second component of the time (0-59). +/// +/// # Returns +/// +/// Returns an `f64` representing the corresponding Excel timestamp. +/// +/// # Example +/// +/// ```rust +/// let timestamp = convert_date(2021, 1, 1, 12, 0, 0); +/// assert_eq!(timestamp, 44204.5); // Represents 2021-01-01 12:00:00 +/// ``` +/// +/// # Panics +/// +/// This function may panic if the provided date and time components are +/// invalid. #[inline] +#[must_use] pub fn convert_date( year: i32, month: i32, @@ -56,7 +140,45 @@ pub fn convert_date( convert_date_windows_1900(year, month, day, hours, minutes, seconds) } +/// Converts a date and time to an Excel timestamp using the Windows 1900 date +/// system. +/// +/// This function takes individual components of a date and time (year, month, +/// day, hours, minutes, and seconds) and converts them into an Excel timestamp +/// specifically for the Windows 1900 date system, which includes the 1900 leap +/// year bug. +/// +/// # Parameters +/// +/// - `year: i32` The year component of the date. +/// +/// - `month: i32` The month component of the date (1-12). +/// +/// - `day: i32` The day component of the date (1-31). +/// +/// - `hours: i32` The hour component of the time (0-23). +/// +/// - `minutes: i32` The minute component of the time (0-59). +/// +/// - `seconds: i32` The second component of the time (0-59). +/// +/// # Returns +/// +/// Returns an `f64` representing the corresponding Excel timestamp. +/// +/// # Example +/// +/// ```rust +/// let timestamp = convert_date_windows_1900(2021, 1, 1, 12, 0, 0); +/// assert_eq!(timestamp, 44204.5); // Represents 2021-01-01 12:00:00 +/// ``` +/// +/// # Panics +/// +/// This function may panic if the provided date and time components are +/// invalid. #[inline] +#[must_use] pub fn convert_date_windows_1900( year: i32, month: i32, @@ -68,7 +190,46 @@ pub fn convert_date_windows_1900( convert_date_crate(year, month, day, hours, minutes, seconds, true) } +/// Converts a date and time to an Excel timestamp using the Mac 1904 date +/// system. +/// +/// This function takes individual components of a date and time (year, month, +/// day, hours, minutes, and seconds) and converts them into an Excel timestamp +/// specifically for the Mac 1904 date system, which has a different base date +/// compared to the Windows 1900 date system. +/// +/// # Parameters +/// +/// - `year: i32` The year component of the date. +/// +/// - `month: i32` The month component of the date (1-12). +/// +/// - `day: i32` The day component of the date (1-31). +/// +/// - `hours: i32` The hour component of the time (0-23). +/// +/// - `minutes: i32` The minute component of the time (0-59). +/// +/// - `seconds: i32` The second component of the time (0-59). +/// +/// # Returns +/// +/// Returns an `f64` representing the corresponding Excel timestamp for the Mac +/// 1904 date system. +/// +/// # Example +/// +/// ```rust +/// let timestamp = convert_date_mac_1904(2021, 1, 1, 12, 0, 0); +/// assert_eq!(timestamp, 44204.5); // Represents 2021-01-01 12:00:00 +/// ``` +/// +/// # Panics +/// +/// This function may panic if the provided date and time components are +/// invalid. #[inline] +#[must_use] pub fn convert_date_mac_1904( year: i32, month: i32, @@ -80,7 +241,53 @@ pub fn convert_date_mac_1904( convert_date_crate(year, month, day, hours, minutes, seconds, false) } -fn convert_date_crate( +/// Converts a date and time to an Excel timestamp based on the specified +/// calendar system. +/// +/// This function takes individual components of a date and time (year, month, +/// day, hours, minutes, and seconds) and converts them into an Excel timestamp. +/// The conversion can be based on either the Windows 1900 date system or the +/// Mac 1904 date system, depending on the value of the +/// `is_calendar_windows_1900` parameter. +/// +/// # Parameters +/// +/// - `year: i32` The year component of the date. +/// +/// - `month: i32` The month component of the date (1-12). +/// +/// - `day: i32` The day component of the date (1-31). +/// +/// - `hours: i32` The hour component of the time (0-23). +/// +/// - `minutes: i32` The minute component of the time (0-59). +/// +/// - `seconds: i32` The second component of the time (0-59). +/// +/// - `is_calendar_windows_1900: bool` A boolean indicating whether to use the +/// Windows 1900 date system (`true`) or the Mac 1904 date system (`false`). +/// +/// # Returns +/// +/// Returns an `f64` representing the corresponding Excel timestamp for the +/// specified calendar system. +/// +/// # Example +/// +/// ```rust +/// let timestamp_windows = convert_date_crate(2021, 1, 1, 12, 0, 0, true); +/// assert_eq!(timestamp_windows, 44204.5); // Represents 2021-01-01 12:00:00 in Windows 1900 system +/// +/// let timestamp_mac = convert_date_crate(2021, 1, 1, 12, 0, 0, false); +/// assert_eq!(timestamp_mac, 44204.5); // Represents 2021-01-01 12:00:00 in Mac 1904 system +/// ``` +/// +/// # Panics +/// +/// This function may panic if the provided date and time components are +/// invalid. +#[must_use] +pub fn convert_date_crate( year: i32, month: i32, day: i32, @@ -89,41 +296,36 @@ fn convert_date_crate( seconds: i32, is_calendar_windows_1900: bool, ) -> f64 { - let mut year = year; - let mut month = month; - let mut myexcel_base_date = 0; - let mut excel1900is_leap_year = 0; - - if is_calendar_windows_1900 { - excel1900is_leap_year = 1; - if &year == &1900 && &month <= &2 { - excel1900is_leap_year = 0; - } - myexcel_base_date = 2415020; + // Initialize the base date and leap year for the calendar + let (base_date, is_leap_year) = if is_calendar_windows_1900 { + let is_leap = i32::from(!(year == 1900 && month <= 2)); + (2_415_020, is_leap) } else { - myexcel_base_date = 2416481; - } + (2_416_481, 0) + }; - // Julian base date Adjustment - if month > 2 { - month -= 3; + // Adjust month and year for Julian date calculation + let (year_adj, month_adj) = if month > 2 { + (year, month - 3) } else { - month += 9; - year -= 1; - } + (year - 1, month + 9) + }; - // Calculate the Julian Date, then subtract the Excel base date (JD 2415020 = 31-Dec-1899 Giving Excel Date of 0) - let century = (year.to_string()[0..2]).parse::().unwrap(); - let decade = (year.to_string()[2..4]).parse::().unwrap(); + // Calculate the Julian date components + let century = year_adj / 100; + let decade = year_adj % 100; - let excel_date = ((146097 * century) / 4) as i32 - + ((1461 * decade) / 4) as i32 - + ((153 * month + 2) / 5) as i32 + let julian_date = ((146_097 * century) / 4) + + ((1461 * decade) / 4) + + ((153 * month_adj + 2) / 5) + day - + 1721119 - - myexcel_base_date - + excel1900is_leap_year; - let excel_time = ((hours * 3600) + (minutes * 60) + seconds) as f64 / 86400 as f64; + + 1_721_119 + - base_date + + is_leap_year; + + // Calculate the time portion of the date + let time_in_days = f64::from(hours * 3600 + minutes * 60 + seconds) / 86400.0; - return (excel_date as f64 + excel_time) as f64; + // Return the final Excel date and time + f64::from(julian_date) + time_in_days } diff --git a/src/helper/formula.rs b/src/helper/formula.rs index c3d6dba4..196a36d9 100644 --- a/src/helper/formula.rs +++ b/src/helper/formula.rs @@ -1,14 +1,28 @@ -use crate::helper::address::*; -use crate::helper::coordinate::*; -use crate::helper::coordinate::*; -use crate::helper::range::*; -use crate::structs::StringValue; -use fancy_regex::{Captures, Regex}; - -/** PARTLY BASED ON: */ -/** Copyright (c) 2007 E. W. Bachtal, Inc. */ -/** https://ewbi.blogs.com/develops/2007/03/excel_formula_p.html */ -/** https://ewbi.blogs.com/develops/2004/12/excel_formula_p.html */ +use crate::{ + helper::{ + address::{ + join_address, + split_address, + }, + coordinate::{ + adjustment_insert_coordinate, + adjustment_remove_coordinate, + coordinate_from_index_with_lock, + index_from_coordinate, + }, + range::{ + get_join_range, + get_split_range, + }, + utils::compile_regex, + }, + structs::StringValue, +}; + +/// PARTLY BASED ON: */ +/// Copyright (c) 2007 E. W. Bachtal, Inc. */ +/// */ +/// #[derive(Clone, Debug, PartialEq)] pub enum FormulaTokenTypes { @@ -42,24 +56,25 @@ pub enum FormulaTokenSubTypes { #[derive(Clone, Debug)] pub struct FormulaToken { - value: StringValue, - token_type: FormulaTokenTypes, + value: StringValue, + token_type: FormulaTokenTypes, token_sub_type: FormulaTokenSubTypes, } impl Default for FormulaToken { #[inline] fn default() -> Self { Self { - value: StringValue::default(), - token_type: FormulaTokenTypes::Unknown, + value: StringValue::default(), + token_type: FormulaTokenTypes::Unknown, token_sub_type: FormulaTokenSubTypes::Nothing, } } } impl FormulaToken { #[inline] + #[must_use] pub fn get_value(&self) -> &str { - self.value.get_value_str() + self.value.value_str() } #[inline] @@ -69,6 +84,7 @@ impl FormulaToken { } #[inline] + #[must_use] pub fn get_token_type(&self) -> &FormulaTokenTypes { &self.token_type } @@ -80,6 +96,7 @@ impl FormulaToken { } #[inline] + #[must_use] pub fn get_token_sub_type(&self) -> &FormulaTokenSubTypes { &self.token_sub_type } @@ -108,669 +125,738 @@ const OPERATORS_SN: &str = "+-"; const OPERATORS_INFIX: &str = "+-*/^&=><"; const OPERATORS_POSTFIX: &str = "%"; -pub const ERRORS: &'static [&'static str] = &[ +pub const ERRORS: &[&str] = &[ "#NULL!", "#DIV/0!", "#VALUE!", "#REF!", "#NAME?", "#NUM!", "#N/A", ]; -const COMPARATORS_MULTI: &'static [&'static str] = &[">=", "<=", "<>"]; +const COMPARATORS_MULTI: &[&str] = &[">=", "<=", "<>"]; -lazy_static! { - pub static ref SCIENTIFIC_REGEX: Regex = Regex::new(r#"/^[1-9]{1}(\\.\\d+)?E{1}$/"#).unwrap(); +macro_rules! token { + ($value:expr, $typ:expr, $sub:expr) => {{ + let mut obj = FormulaToken::default(); + obj.set_value($value); + obj.set_token_type($typ); + obj.set_token_sub_type($sub); + obj + }}; } pub(crate) fn parse_to_tokens>(formula: S) -> Vec { - let mut tokens: Vec = Vec::new(); + let formula_str = formula.into(); + let formula_length = formula_str.chars().count(); - let formula = formula.into(); - let formula_length = formula.chars().count(); - if formula_length < 2 || formula.chars().next().unwrap() != '=' { - return tokens; + // quick checks + if formula_length < 2 || !formula_str.starts_with('=') { + return Vec::new(); } - // Helper variables + let (_, tokens2) = parse_into_intermediate_tokens(&formula_str); + finalize_tokens(&tokens2) +} + +fn parse_into_intermediate_tokens(formula: &str) -> (Vec, Vec) { let mut tokens1: Vec = Vec::new(); let mut tokens2: Vec = Vec::new(); let mut stack: Vec = Vec::new(); + // state flags let mut in_string = false; let mut in_path = false; let mut in_range = false; let mut in_error = false; - let mut next_token: Option = None; let mut index = 1; - let mut value = String::from(""); + let mut value = String::new(); + let formula_length = formula.chars().count(); while index < formula_length { // double-quoted strings - // embeds are doubled - // end marks token if in_string { - if formula.chars().nth(index).unwrap() == self::QUOTE_DOUBLE { - if ((index + 2) <= formula_length) - && (formula.chars().nth(index + 1).unwrap() == self::QUOTE_DOUBLE) - { - value = format!("{}{}", value, self::QUOTE_DOUBLE); - index += 1; - } else { - in_string = false; - let mut obj = FormulaToken::default(); - obj.set_value(value); - obj.set_token_type(FormulaTokenTypes::Operand); - obj.set_token_sub_type(FormulaTokenSubTypes::Text); - tokens1.push(obj); - value = String::from(""); - } - } else { - value = format!("{}{}", value, formula.chars().nth(index).unwrap()); - } - index += 1; - + handle_in_string( + formula, + &mut index, + &mut value, + &mut tokens1, + &mut in_string, + ); continue; } // single-quoted strings (links) - // embeds are double - // end does not mark a token if in_path { - if formula.chars().nth(index).unwrap() == self::QUOTE_SINGLE { - if ((index + 2) <= formula_length) - && (formula.chars().nth(index + 1).unwrap() == self::QUOTE_SINGLE) - { - value = format!("{}{}", value, self::QUOTE_SINGLE); - index += 1; - } else { - in_path = false; - } - } else { - value = format!("{}{}", value, formula.chars().nth(index).unwrap()); - } - index += 1; - + handle_in_path(formula, &mut index, &mut value, &mut in_path); continue; } - // bracked strings (R1C1 range index or linked workbook name) - // no embeds (changed to "()" by Excel) - // end does not mark a token + // bracketed strings (R1C1 range index or workbook name) if in_range { - if formula.chars().nth(index).unwrap() == self::BRACKET_CLOSE { - in_range = false; - } - value = format!("{}{}", value, formula.chars().nth(index).unwrap()); - index; - + handle_in_range(formula, &mut index, &mut value, &mut in_range); continue; } // error values - // end marks a token, determined from absolute list of values if in_error { - value = format!("{}{}", value, formula.chars().nth(index).unwrap()); - index += 1; - if self::ERRORS.iter().any(|&x| x == value.as_str()) { - in_error = false; - let mut obj = FormulaToken::default(); - obj.set_value(value); - obj.set_token_type(FormulaTokenTypes::Operand); - obj.set_token_sub_type(FormulaTokenSubTypes::Error); - tokens1.push(obj); - value = String::from(""); - } - + handle_in_error(formula, &mut index, &mut value, &mut tokens1, &mut in_error); continue; } // scientific notation check - if self::OPERATORS_SN.contains(formula.chars().nth(index).unwrap()) { - if value.len() > 1 { - if SCIENTIFIC_REGEX - .is_match(&formula.chars().nth(index).unwrap().to_string()) - .unwrap_or(false) - { - value = format!("{}{}", value, formula.chars().nth(index).unwrap()); - index += 1; - - continue; - } - } + if handle_scientific_notation(formula, &mut index, &mut value) { + continue; } - // independent character evaluation (order not important) + // handle special characters + if handle_special_characters( + formula, + &mut index, + &mut value, + &mut tokens1, + &mut stack, + &mut in_string, + &mut in_range, + &mut in_error, + ) { + continue; + } - // establish state-dependent character evaluations - if formula.chars().nth(index).unwrap() == self::QUOTE_DOUBLE { - if value != "" { - // unexpected - let mut obj = FormulaToken::default(); - obj.set_value(value); - obj.set_token_type(FormulaTokenTypes::Unknown); - tokens1.push(obj); - value = String::from(""); - } - in_string = true; - index += 1; + // accumulate as normal char + value.push(formula.chars().nth(index).unwrap()); + index += 1; + } - continue; + // dump remaining + if !value.is_empty() { + let mut obj = FormulaToken::default(); + obj.set_value(value.clone()); + obj.set_token_type(FormulaTokenTypes::Operand); + tokens1.push(obj); + } + + // trim whitespace, handle intersection + cleanup_tokens(&tokens1, &mut tokens2); + + (tokens1, tokens2) +} + +fn finalize_tokens(tokens_in: &[FormulaToken]) -> Vec { + let mut tokens: Vec = Vec::new(); + let token_count = tokens_in.len(); + let mut previous_token: Option; + + for i in 0..token_count { + let mut token = tokens_in[i].clone(); + if i > 0 { + previous_token = Some(tokens_in[i - 1].clone()); + } else { + previous_token = None; } - if formula.chars().nth(index).unwrap() == self::QUOTE_SINGLE { - if value != "" { - // unexpected - let mut obj = FormulaToken::default(); - obj.set_value(value); - obj.set_token_type(FormulaTokenTypes::Unknown); - tokens1.push(obj); - value = String::from(""); + // switch infix "-" to prefix + if token.get_token_type() == &FormulaTokenTypes::OperatorInfix && token.get_value() == "-" { + if i == 0 { + token.set_token_type(FormulaTokenTypes::OperatorPrefix); + } else if should_token_be_math(previous_token) { + token.set_token_sub_type(FormulaTokenSubTypes::Math); + } else { + token.set_token_type(FormulaTokenTypes::OperatorPrefix); } - in_string = true; - index += 1; - + tokens.push(token.clone()); continue; } - if formula.chars().nth(index).unwrap() == self::BRACKET_OPEN { - in_range = true; - value = format!("{}{}", value, self::BRACKET_OPEN); - index += 1; - + // switch infix "+" to prefix/noop + if token.get_token_type() == &FormulaTokenTypes::OperatorInfix && token.get_value() == "+" { + if i == 0 { + // skip + continue; + } else if should_token_be_math(previous_token) { + token.set_token_sub_type(FormulaTokenSubTypes::Math); + } else { + // skip + continue; + } + tokens.push(token.clone()); continue; } - if formula.chars().nth(index).unwrap() == self::ERROR_START { - if value != "" { - // unexpected - let mut obj = FormulaToken::default(); - obj.set_value(value); - obj.set_token_type(FormulaTokenTypes::Unknown); - tokens1.push(obj); - value = String::from(""); + // set operator subtypes + if token.get_token_type() == &FormulaTokenTypes::OperatorInfix + && token.get_token_sub_type() == &FormulaTokenSubTypes::Nothing + { + if let Some(c) = token.get_value().chars().next() { + if "<>=".contains(c) { + token.set_token_sub_type(FormulaTokenSubTypes::Logical); + } else if c == '&' { + token.set_token_sub_type(FormulaTokenSubTypes::Concatenation); + } else { + token.set_token_sub_type(FormulaTokenSubTypes::Math); + } } - in_error = true; - value = format!("{}{}", value, self::ERROR_START); - index += 1; + tokens.push(token.clone()); + continue; + } + // set operand subtypes + if token.get_token_type() == &FormulaTokenTypes::Operand + && token.get_token_sub_type() == &FormulaTokenSubTypes::Nothing + { + if token.get_value().parse::().is_ok() { + token.set_token_sub_type(FormulaTokenSubTypes::Number); + } else if ["TRUE", "FALSE"].contains(&token.get_value().to_uppercase().as_str()) { + token.set_token_sub_type(FormulaTokenSubTypes::Logical); + } else { + token.set_token_sub_type(FormulaTokenSubTypes::Range); + } + tokens.push(token.clone()); continue; } - // mark start and end of arrays and array rows - if formula.chars().nth(index).unwrap() == self::BRACE_OPEN { - if value != "" { - // unexpected - let mut obj = FormulaToken::default(); - obj.set_value(value); - obj.set_token_type(FormulaTokenTypes::Unknown); - tokens1.push(obj); - value = String::from(""); + // remove leading '@' from function + if let FormulaTokenTypes::Function = token.get_token_type() { + let val = token.get_value(); + if val.starts_with('@') { + token.set_value(val.trim_start_matches('@').to_string()); } + } - let mut obj = FormulaToken::default(); - obj.set_value("ARRAY"); - obj.set_token_type(FormulaTokenTypes::Function); - obj.set_token_sub_type(FormulaTokenSubTypes::Start); - tokens1.push(obj.clone()); - stack.push(obj); + tokens.push(token.clone()); + } + tokens +} - let mut obj = FormulaToken::default(); - obj.set_value("ARRAYROW"); - obj.set_token_type(FormulaTokenTypes::Function); - obj.set_token_sub_type(FormulaTokenSubTypes::Start); - tokens1.push(obj.clone()); - stack.push(obj); +// -------------------------------------------------------------------------- +// Below are the smaller "handler" helper functions referenced above. +// -------------------------------------------------------------------------- - index += 1; +fn handle_in_string( + formula: &str, + index: &mut usize, + value: &mut String, + tokens: &mut Vec, + in_string: &mut bool, +) { + if formula.chars().nth(*index).unwrap() == QUOTE_DOUBLE { + // double quote check + if (*index + 2) <= formula.chars().count() + && formula.chars().nth(*index + 1).unwrap() == QUOTE_DOUBLE + { + *value = format!("{value}{QUOTE_DOUBLE}"); + *index += 1; + } else { + *in_string = false; + let mut obj = FormulaToken::default(); + obj.set_value(value.clone()); + obj.set_token_type(FormulaTokenTypes::Operand); + obj.set_token_sub_type(FormulaTokenSubTypes::Text); + tokens.push(obj); + *value = String::new(); + } + } else { + value.push(formula.chars().nth(*index).unwrap()); + } + *index += 1; +} - continue; +fn handle_in_path(formula: &str, index: &mut usize, value: &mut String, in_path: &mut bool) { + if formula.chars().nth(*index).unwrap() == QUOTE_SINGLE { + if (*index + 2) <= formula.chars().count() + && formula.chars().nth(*index + 1).unwrap() == QUOTE_SINGLE + { + *value = format!("{value}{QUOTE_SINGLE}"); + *index += 1; + } else { + *in_path = false; } + } else { + value.push(formula.chars().nth(*index).unwrap()); + } + *index += 1; +} - if formula.chars().nth(index).unwrap() == self::SEMICOLON { - if value != "" { - let mut obj = FormulaToken::default(); - obj.set_value(value); - obj.set_token_type(FormulaTokenTypes::Operand); - tokens1.push(obj); - value = String::from(""); - } +fn handle_in_range(formula: &str, index: &mut usize, value: &mut String, in_range: &mut bool) { + if formula.chars().nth(*index).unwrap() == BRACKET_CLOSE { + *in_range = false; + } + value.push(formula.chars().nth(*index).unwrap()); + *index += 1; +} - let mut obj = stack.pop().unwrap(); - obj.set_value(""); - obj.set_token_sub_type(FormulaTokenSubTypes::Stop); - tokens1.push(obj); +fn handle_in_error( + formula: &str, + index: &mut usize, + value: &mut String, + tokens: &mut Vec, + in_error: &mut bool, +) { + value.push(formula.chars().nth(*index).unwrap()); + *index += 1; + if ERRORS.contains(&value.as_str()) { + *in_error = false; + let mut obj = FormulaToken::default(); + obj.set_value(value.clone()); + obj.set_token_type(FormulaTokenTypes::Operand); + obj.set_token_sub_type(FormulaTokenSubTypes::Error); + tokens.push(obj); + *value = String::new(); + } +} +fn handle_scientific_notation(formula: &str, index: &mut usize, value: &mut String) -> bool { + if let Some(current_char) = formula.chars().nth(*index) { + if OPERATORS_SN.contains(current_char) + && value.len() > 1 + && compile_regex!(r"/^[1-9]{1}(\\.\\d+)?E{1}$/") + .is_match(¤t_char.to_string()) + .unwrap_or(false) + { + value.push(current_char); + *index += 1; + return true; + } + } + false +} + +#[allow(clippy::too_many_arguments)] +fn handle_special_characters( + formula: &str, + index: &mut usize, + value: &mut String, + tokens1: &mut Vec, + stack: &mut Vec, + in_string: &mut bool, + in_range: &mut bool, + in_error: &mut bool, +) -> bool { + let current_char = formula.chars().nth(*index).unwrap(); + + // handle double quote + if current_char == QUOTE_DOUBLE { + if !value.is_empty() { let mut obj = FormulaToken::default(); - obj.set_value(","); - obj.set_token_type(FormulaTokenTypes::Argument); + obj.set_value(value.clone()); + obj.set_token_type(FormulaTokenTypes::Unknown); tokens1.push(obj); + value.clear(); + } + *in_string = true; + *index += 1; + return true; + } + // handle single quote + if current_char == QUOTE_SINGLE { + if !value.is_empty() { let mut obj = FormulaToken::default(); - obj.set_value("ARRAYROW"); - obj.set_token_type(FormulaTokenTypes::Function); - obj.set_token_sub_type(FormulaTokenSubTypes::Start); - tokens1.push(obj.clone()); - stack.push(obj); - - index += 1; - - continue; + obj.set_value(value.clone()); + obj.set_token_type(FormulaTokenTypes::Unknown); + tokens1.push(obj); + value.clear(); } + *in_string = true; + *index += 1; + return true; + } - if formula.chars().nth(index).unwrap() == self::BRACE_CLOSE { - if value != "" { - let mut obj = FormulaToken::default(); - obj.set_value(value); - obj.set_token_type(FormulaTokenTypes::Operand); - tokens1.push(obj); - value = String::from(""); - } - - let mut obj = stack.pop().unwrap().clone(); - obj.set_value(""); - obj.set_token_sub_type(FormulaTokenSubTypes::Stop); - tokens1.push(obj); + // handle bracket open + if current_char == BRACKET_OPEN { + *in_range = true; + value.push(BRACKET_OPEN); + *index += 1; + return true; + } - let mut obj = stack.pop().unwrap().clone(); - obj.set_value(""); - obj.set_token_sub_type(FormulaTokenSubTypes::Stop); + // handle error start + if current_char == ERROR_START { + if !value.is_empty() { + let mut obj = FormulaToken::default(); + obj.set_value(value.clone()); + obj.set_token_type(FormulaTokenTypes::Unknown); tokens1.push(obj); + value.clear(); + } + *in_error = true; + value.push(ERROR_START); + *index += 1; + return true; + } - index += 1; + // handle braces and semicolons (array constructs) + if handle_array_chars(current_char, value, tokens1, stack) { + *index += 1; + return true; + } - continue; + // handle whitespace + if current_char == WHITESPACE { + handle_whitespace(value, tokens1); + *index += 1; + // skip consecutive spaces + let len = formula.chars().count(); + while *index < len && formula.chars().nth(*index).unwrap() == WHITESPACE { + *index += 1; } + return true; + } - // trim white-space - if formula.chars().nth(index).unwrap() == self::WHITESPACE { - if value != "" { - let mut obj = FormulaToken::default(); - obj.set_value(value); - obj.set_token_type(FormulaTokenTypes::Operand); - tokens1.push(obj); - value = String::from(""); - } + // handle multi-character comparators + if handle_multi_char_comparator(formula, index, value, tokens1) { + return true; + } + + // handle infix operators + if OPERATORS_INFIX.contains(current_char) { + if !value.is_empty() { let mut obj = FormulaToken::default(); - obj.set_value(""); - obj.set_token_type(FormulaTokenTypes::Whitespace); + obj.set_value(value.clone()); + obj.set_token_type(FormulaTokenTypes::Operand); tokens1.push(obj); - index += 1; - while ((formula.chars().nth(index).unwrap() == self::WHITESPACE) - && (index < formula_length)) - { - index += 1; - } - - continue; + value.clear(); } + let mut obj = FormulaToken::default(); + obj.set_value(current_char); + obj.set_token_type(FormulaTokenTypes::OperatorInfix); + tokens1.push(obj); + *index += 1; + return true; + } - // multi-character comparators - if (index + 2) <= formula_length { - if COMPARATORS_MULTI - .iter() - .any(|&x| x == formula.chars().skip(index).take(2).collect::()) - { - if value != "" { - let mut obj = FormulaToken::default(); - obj.set_value(value); - obj.set_token_type(FormulaTokenTypes::Operand); - tokens1.push(obj); - value = String::from(""); - } - let mut obj = FormulaToken::default(); - obj.set_value(formula.chars().skip(index).take(2).collect::()); - obj.set_token_type(FormulaTokenTypes::OperatorInfix); - obj.set_token_sub_type(FormulaTokenSubTypes::Logical); - tokens1.push(obj); - index += 2; + // handle postfix operators + if OPERATORS_POSTFIX.contains(current_char) { + if !value.is_empty() { + let mut obj = FormulaToken::default(); + obj.set_value(value.clone()); + obj.set_token_type(FormulaTokenTypes::Operand); + tokens1.push(obj); + value.clear(); + } + let mut obj = FormulaToken::default(); + obj.set_value(current_char); + obj.set_token_type(FormulaTokenTypes::OperatorPostfix); + tokens1.push(obj); + *index += 1; + return true; + } - continue; - } + // handle subexpression / function start + if current_char == PAREN_OPEN { + if value.is_empty() { + let obj = token!( + "", + FormulaTokenTypes::Subexpression, + FormulaTokenSubTypes::Start + ); + tokens1.push(obj.clone()); + stack.push(obj); + } else { + let obj = token!( + value.clone(), + FormulaTokenTypes::Function, + FormulaTokenSubTypes::Start + ); + tokens1.push(obj.clone()); + stack.push(obj); + value.clear(); } + *index += 1; + return true; + } - // standard infix operators - if self::OPERATORS_INFIX.contains(formula.chars().nth(index).unwrap()) { - if value != "" { - let mut obj = FormulaToken::default(); - obj.set_value(value); - obj.set_token_type(FormulaTokenTypes::Operand); - tokens1.push(obj); - value = String::from(""); - } + // handle function/operand unions + if current_char == COMMA { + if !value.is_empty() { let mut obj = FormulaToken::default(); - obj.set_value(formula.chars().nth(index).unwrap()); - obj.set_token_type(FormulaTokenTypes::OperatorInfix); + obj.set_value(value.clone()); + obj.set_token_type(FormulaTokenTypes::Operand); tokens1.push(obj); - index += 1; + value.clear(); + } - continue; + let mut obj = stack.pop().unwrap(); + obj.set_value(""); + obj.set_token_sub_type(FormulaTokenSubTypes::Stop); + stack.push(obj.clone()); + + if obj.get_token_type() == &FormulaTokenTypes::Function { + let mut op = FormulaToken::default(); + op.set_value(","); + op.set_token_type(FormulaTokenTypes::OperatorInfix); + op.set_token_sub_type(FormulaTokenSubTypes::Union); + tokens1.push(op); + } else { + let mut arg = FormulaToken::default(); + arg.set_value(","); + arg.set_token_type(FormulaTokenTypes::Argument); + tokens1.push(arg); } + *index += 1; + return true; + } - // standard postfix operators (only one) - if self::OPERATORS_POSTFIX.contains(formula.chars().nth(index).unwrap()) { - if value != "" { - let mut obj = FormulaToken::default(); - obj.set_value(value); - obj.set_token_type(FormulaTokenTypes::Operand); - tokens1.push(obj); - value = String::from(""); - } + // handle subexpression/function stop + if current_char == PAREN_CLOSE { + if !value.is_empty() { let mut obj = FormulaToken::default(); - obj.set_value(formula.chars().nth(index).unwrap()); - obj.set_token_type(FormulaTokenTypes::OperatorPostfix); + obj.set_value(value.clone()); + obj.set_token_type(FormulaTokenTypes::Operand); tokens1.push(obj); - index += 1; - - continue; + value.clear(); } + let mut obj = stack.pop().unwrap(); + obj.set_value(""); + obj.set_token_sub_type(FormulaTokenSubTypes::Stop); + tokens1.push(obj); + *index += 1; + return true; + } - // start subexpression or function - if formula.chars().nth(index).unwrap() == self::PAREN_OPEN { - if value != "" { - let mut obj = FormulaToken::default(); - obj.set_value(value); - obj.set_token_type(FormulaTokenTypes::Function); - obj.set_token_sub_type(FormulaTokenSubTypes::Start); - tokens1.push(obj.clone()); - stack.push(obj); - value = String::from(""); - } else { - let mut obj = FormulaToken::default(); - obj.set_value(""); - obj.set_token_type(FormulaTokenTypes::Subexpression); - obj.set_token_sub_type(FormulaTokenSubTypes::Start); - tokens1.push(obj.clone()); - stack.push(obj); - } - index += 1; + false +} - continue; +fn handle_array_chars( + current_char: char, + value: &mut String, + tokens1: &mut Vec, + stack: &mut Vec, +) -> bool { + // handle { + if current_char == BRACE_OPEN { + if !value.is_empty() { + let mut obj = FormulaToken::default(); + obj.set_value(value.clone()); + obj.set_token_type(FormulaTokenTypes::Unknown); + tokens1.push(obj); + value.clear(); } + let arr_start = token!( + "ARRAY", + FormulaTokenTypes::Function, + FormulaTokenSubTypes::Start + ); + tokens1.push(arr_start.clone()); + stack.push(arr_start); - // function, subexpression, or array parameters, or operand unions - if formula.chars().nth(index).unwrap() == self::COMMA { - if value != "" { - let mut obj = FormulaToken::default(); - obj.set_value(value); - obj.set_token_type(FormulaTokenTypes::Operand); - tokens1.push(obj); - value = String::from(""); - } - - let mut obj = stack.pop().unwrap(); - obj.set_value(""); - obj.set_token_sub_type(FormulaTokenSubTypes::Stop); - stack.push(obj.clone()); - - if obj.get_token_type() == &FormulaTokenTypes::Function { - let mut obj = FormulaToken::default(); - obj.set_value(","); - obj.set_token_type(FormulaTokenTypes::OperatorInfix); - obj.set_token_sub_type(FormulaTokenSubTypes::Union); - tokens1.push(obj); - } else { - let mut obj = FormulaToken::default(); - obj.set_value(","); - obj.set_token_type(FormulaTokenTypes::Argument); - tokens1.push(obj); - } - index += 1; + let arr_row = token!( + "ARRAYROW", + FormulaTokenTypes::Function, + FormulaTokenSubTypes::Start + ); + tokens1.push(arr_row.clone()); + stack.push(arr_row); + return true; + } - continue; + // handle ; + if current_char == SEMICOLON { + if !value.is_empty() { + let mut obj = FormulaToken::default(); + obj.set_value(value.clone()); + obj.set_token_type(FormulaTokenTypes::Operand); + tokens1.push(obj); + value.clear(); } + let mut obj = stack.pop().unwrap(); + obj.set_value(""); + obj.set_token_sub_type(FormulaTokenSubTypes::Stop); + tokens1.push(obj); - // stop subexpression - if formula.chars().nth(index).unwrap() == self::PAREN_CLOSE { - if value != "" { - let mut obj = FormulaToken::default(); - obj.set_value(value); - obj.set_token_type(FormulaTokenTypes::Operand); - tokens1.push(obj); - value = String::from(""); - } - - let mut obj = stack.pop().unwrap(); - obj.set_value(""); - obj.set_token_sub_type(FormulaTokenSubTypes::Stop); - tokens1.push(obj); + let mut comma = FormulaToken::default(); + comma.set_value(","); + comma.set_token_type(FormulaTokenTypes::Argument); + tokens1.push(comma); - index += 1; + let arr_row = token!( + "ARRAYROW", + FormulaTokenTypes::Function, + FormulaTokenSubTypes::Start + ); + tokens1.push(arr_row.clone()); + stack.push(arr_row); + return true; + } - continue; + // handle } + if current_char == BRACE_CLOSE { + if !value.is_empty() { + let mut obj = FormulaToken::default(); + obj.set_value(value.clone()); + obj.set_token_type(FormulaTokenTypes::Operand); + tokens1.push(obj); + value.clear(); } - - // token accumulation - value = format!("{}{}", value, formula.chars().nth(index).unwrap()); - index += 1; + let mut obj_end = stack.pop().unwrap(); + obj_end.set_value(""); + obj_end.set_token_sub_type(FormulaTokenSubTypes::Stop); + tokens1.push(obj_end); + + let mut arr_end = stack.pop().unwrap(); + arr_end.set_value(""); + arr_end.set_token_sub_type(FormulaTokenSubTypes::Stop); + tokens1.push(arr_end); + return true; } - // dump remaining accumulation - if value != "" { + false +} + +fn handle_whitespace(value: &mut String, tokens1: &mut Vec) { + if !value.is_empty() { let mut obj = FormulaToken::default(); obj.set_value(value.clone()); obj.set_token_type(FormulaTokenTypes::Operand); tokens1.push(obj); + value.clear(); } + let mut space = FormulaToken::default(); + space.set_value(""); + space.set_token_type(FormulaTokenTypes::Whitespace); + tokens1.push(space); +} - // move tokenList to new set, excluding unnecessary white-space tokens and converting necessary ones to intersections - let token_count = tokens1.len(); - let mut previous_token = None; - let mut next_token = None; - for i in 0..token_count { - let token = tokens1.get(i).unwrap(); - if i > 0 { - match tokens1.get((i - 1)) { - Some(v) => { - previous_token = Some(v.clone()); - } - None => { - previous_token = None; - } - } - } else { - previous_token = None; - } - - match tokens1.get((i + 1)) { - Some(v) => { - next_token = Some(tokens1.get((i + 1)).unwrap()); - } - None => { - next_token = None; +fn handle_multi_char_comparator( + formula: &str, + index: &mut usize, + value: &mut String, + tokens1: &mut Vec, +) -> bool { + let formula_length = formula.chars().count(); + if (*index + 2) <= formula_length { + let next_two = formula.chars().skip(*index).take(2).collect::(); + if COMPARATORS_MULTI.iter().any(|&x| x == next_two) { + // flush current value + if !value.is_empty() { + let mut obj = FormulaToken::default(); + obj.set_value(value.clone()); + obj.set_token_type(FormulaTokenTypes::Operand); + tokens1.push(obj); + value.clear(); } + let mut obj = FormulaToken::default(); + obj.set_value(next_two); + obj.set_token_type(FormulaTokenTypes::OperatorInfix); + obj.set_token_sub_type(FormulaTokenSubTypes::Logical); + tokens1.push(obj); + *index += 2; + return true; } + } + false +} +fn cleanup_tokens(tokens1: &[FormulaToken], tokens2: &mut Vec) { + let token_count = tokens1.len(); + let mut value = String::new(); + for i in 0..token_count { + let token = &tokens1[i]; if token.get_token_type() != &FormulaTokenTypes::Whitespace { tokens2.push(token.clone()); - continue; } - if previous_token.is_none() { - continue; - } - - if !(((previous_token.as_ref().unwrap().get_token_type() == &FormulaTokenTypes::Function) - && (previous_token.as_ref().unwrap().get_token_sub_type() - == &FormulaTokenSubTypes::Stop)) - || ((previous_token.as_ref().unwrap().get_token_type() - == &FormulaTokenTypes::Subexpression) - && (previous_token.as_ref().unwrap().get_token_sub_type() - == &FormulaTokenSubTypes::Stop)) - || (previous_token.as_ref().unwrap().get_token_type() == &FormulaTokenTypes::Operand)) - { - continue; - } - - if next_token.is_none() { - continue; - } - - if !(((next_token.as_ref().unwrap().get_token_type() == &FormulaTokenTypes::Function) - && (next_token.as_ref().unwrap().get_token_sub_type() == &FormulaTokenSubTypes::Start)) - || ((next_token.as_ref().unwrap().get_token_type() - == &FormulaTokenTypes::Subexpression) - && (next_token.as_ref().unwrap().get_token_sub_type() - == &FormulaTokenSubTypes::Start)) - || (next_token.as_ref().unwrap().get_token_type() == &FormulaTokenTypes::Operand)) - { - continue; - } - - let mut obj = FormulaToken::default(); - obj.set_value(value); - obj.set_token_type(FormulaTokenTypes::OperatorInfix); - obj.set_token_sub_type(FormulaTokenSubTypes::Intersection); - tokens2.push(obj); - value = String::from(""); - } + let mut previous_token: Option<&FormulaToken> = None; + let mut next_token: Option<&FormulaToken> = None; - // move tokens to final list, switching infix "-" operators to prefix when appropriate, switching infix "+" operators - // to noop when appropriate, identifying operand and infix-operator subtypes, and pulling "@" from function names - let token_count = tokens2.len(); - let mut previous_token = None; - for i in 0..token_count { - let mut token = tokens2.get(i).unwrap().clone(); if i > 0 { - match tokens2.get(i - 1) { - Some(v) => { - previous_token = Some(v.clone()); - } - None => { - previous_token = None; - } - } - } else { - previous_token = None; + previous_token = tokens1.get(i - 1); } - - if token.get_token_type() == &FormulaTokenTypes::OperatorInfix && token.get_value() == "-" { - if i == 0 { - token.set_token_type(FormulaTokenTypes::OperatorPrefix); - } else if ((previous_token.as_ref().unwrap().get_token_type() - == &FormulaTokenTypes::Function) - && (previous_token.as_ref().unwrap().get_token_sub_type() - == &FormulaTokenSubTypes::Stop)) - || ((previous_token.as_ref().unwrap().get_token_type() - == &FormulaTokenTypes::Subexpression) - && (previous_token.as_ref().unwrap().get_token_sub_type() - == &FormulaTokenSubTypes::Stop)) - || (previous_token.as_ref().unwrap().get_token_type() - == &FormulaTokenTypes::OperatorPostfix) - || (previous_token.as_ref().unwrap().get_token_type() - == &FormulaTokenTypes::Operand) - { - token.set_token_sub_type(FormulaTokenSubTypes::Math); - } else { - token.set_token_type(FormulaTokenTypes::OperatorPrefix); - } - - tokens.push(token.clone()); - - continue; + if i + 1 < token_count { + next_token = tokens1.get(i + 1); } - if token.get_token_type() == &FormulaTokenTypes::OperatorInfix && token.get_value() == "+" { - if i == 0 { + if let Some(p) = previous_token { + if !is_operand_or_close(p) { continue; - } else if ((previous_token.as_ref().unwrap().get_token_type() - == &FormulaTokenTypes::Function) - && (previous_token.as_ref().unwrap().get_token_sub_type() - == &FormulaTokenSubTypes::Stop)) - || ((previous_token.as_ref().unwrap().get_token_type() - == &FormulaTokenTypes::Subexpression) - && (previous_token.as_ref().unwrap().get_token_sub_type() - == &FormulaTokenSubTypes::Stop)) - || (previous_token.as_ref().unwrap().get_token_type() - == &FormulaTokenTypes::OperatorPostfix) - || (previous_token.as_ref().unwrap().get_token_type() - == &FormulaTokenTypes::Operand) - { - token.set_token_sub_type(FormulaTokenSubTypes::Math); - } else { - continue; - } - - tokens.push(token.clone()); - - continue; - } - - if token.get_token_type() == &FormulaTokenTypes::OperatorInfix - && token.get_token_sub_type() == &FormulaTokenSubTypes::Nothing - { - if "<>=".contains(token.get_value().chars().nth(0).unwrap()) { - token.set_token_sub_type(FormulaTokenSubTypes::Logical); - } else if token.get_value() == "&" { - token.set_token_sub_type(FormulaTokenSubTypes::Concatenation); - } else { - token.set_token_sub_type(FormulaTokenSubTypes::Math); } - - tokens.push(token.clone()); - - continue; } - - if token.get_token_type() == &FormulaTokenTypes::Operand - && token.get_token_sub_type() == &FormulaTokenSubTypes::Nothing - { - if !token.get_value().parse::().is_ok() { - if token.get_value().to_uppercase() == "TRUE" - || token.get_value().to_uppercase() == "FALSE" - { - token.set_token_sub_type(FormulaTokenSubTypes::Logical); - } else { - token.set_token_sub_type(FormulaTokenSubTypes::Range); - } - } else { - token.set_token_sub_type(FormulaTokenSubTypes::Number); + if let Some(n) = next_token { + if !is_operand_or_open(n) { + continue; } - - tokens.push(token.clone()); - - continue; } - - if token.get_token_type() == &FormulaTokenTypes::Function { - if token.get_value() != "" { - if token.get_value().chars().nth(0).unwrap() == '@' { - token.set_value(token.get_value().chars().skip(1).collect::()); - } + tokens2.push(token!( + value.clone(), + FormulaTokenTypes::OperatorInfix, + FormulaTokenSubTypes::Intersection + )); + value.clear(); + + if let Some(n) = next_token { + if !is_operand_or_open(n) { + continue; } } + tokens2.push(token!( + value.clone(), + FormulaTokenTypes::OperatorInfix, + FormulaTokenSubTypes::Intersection + )); + value.clear(); + } +} - tokens.push(token.clone()); +#[allow(clippy::ref_option_ref)] +fn should_token_be_math(previous_token: Option) -> bool { + if previous_token.is_none() { + return false; } - tokens + let t = previous_token.unwrap(); + if (t.get_token_type() == &FormulaTokenTypes::Function + && t.get_token_sub_type() == &FormulaTokenSubTypes::Stop) + || (t.get_token_type() == &FormulaTokenTypes::Subexpression + && t.get_token_sub_type() == &FormulaTokenSubTypes::Stop) + || (t.get_token_type() == &FormulaTokenTypes::OperatorPostfix) + || (t.get_token_type() == &FormulaTokenTypes::Operand) + { + return true; + } + false +} + +fn is_operand_or_close(token: &FormulaToken) -> bool { + ((token.get_token_type() == &FormulaTokenTypes::Function) + && (token.get_token_sub_type() == &FormulaTokenSubTypes::Stop)) + || ((token.get_token_type() == &FormulaTokenTypes::Subexpression) + && (token.get_token_sub_type() == &FormulaTokenSubTypes::Stop)) + || (token.get_token_type() == &FormulaTokenTypes::Operand) +} + +fn is_operand_or_open(token: &FormulaToken) -> bool { + ((token.get_token_type() == &FormulaTokenTypes::Function) + && (token.get_token_sub_type() == &FormulaTokenSubTypes::Start)) + || ((token.get_token_type() == &FormulaTokenTypes::Subexpression) + && (token.get_token_sub_type() == &FormulaTokenSubTypes::Start)) + || (token.get_token_type() == &FormulaTokenTypes::Operand) } pub(crate) fn render(formula_token_list: &[FormulaToken]) -> String { - let mut result = String::from(""); + let mut result = String::new(); for token in formula_token_list { if token.get_token_type() == &FormulaTokenTypes::Function && token.get_token_sub_type() == &FormulaTokenSubTypes::Start { result = format!("{}{}", result, token.get_value()); - result = format!("{}{}", result, self::PAREN_OPEN); + result = format!("{result}{PAREN_OPEN}"); } else if token.get_token_type() == &FormulaTokenTypes::Function && token.get_token_sub_type() == &FormulaTokenSubTypes::Stop { - result = format!("{}{}", result, self::PAREN_CLOSE); + result = format!("{result}{PAREN_CLOSE}"); } else if token.get_token_type() == &FormulaTokenTypes::Subexpression && token.get_token_sub_type() == &FormulaTokenSubTypes::Start { - result = format!("{}{}", result, self::PAREN_OPEN); + result = format!("{result}{PAREN_OPEN}"); } else if token.get_token_type() == &FormulaTokenTypes::Subexpression && token.get_token_sub_type() == &FormulaTokenSubTypes::Stop { - result = format!("{}{}", result, self::PAREN_CLOSE); + result = format!("{result}{PAREN_CLOSE}"); } else if token.get_token_type() == &FormulaTokenTypes::Operand && token.get_token_sub_type() == &FormulaTokenSubTypes::Text { - result = format!("{}{}", result, self::QUOTE_DOUBLE); + result = format!("{result}{QUOTE_DOUBLE}"); result = format!("{}{}", result, token.get_value()); - result = format!("{}{}", result, self::QUOTE_DOUBLE); + result = format!("{result}{QUOTE_DOUBLE}"); } else if token.get_token_type() == &FormulaTokenTypes::OperatorInfix && token.get_token_sub_type() == &FormulaTokenSubTypes::Intersection { - result = format!("{}{}", result, self::WHITESPACE); + result = format!("{result}{WHITESPACE}"); } else { result = format!("{}{}", result, token.get_value()); } @@ -780,10 +866,10 @@ pub(crate) fn render(formula_token_list: &[FormulaToken]) -> String { pub fn adjustment_formula_coordinate( token_list: &mut [FormulaToken], - offset_col_num: &i32, - offset_row_num: &i32, + offset_col_num: i32, + offset_row_num: i32, ) { - for token in token_list.into_iter() { + for token in token_list.iter_mut() { if token.get_token_type() == &FormulaTokenTypes::Operand && token.get_token_sub_type() == &FormulaTokenSubTypes::Range { @@ -799,32 +885,28 @@ pub fn adjustment_formula_coordinate( let is_lock_col = cell.2.unwrap(); let is_lock_row = cell.3.unwrap(); if !is_lock_col { - let calc_col_num = col_num as i32 + offset_col_num; + let calc_col_num = + num_traits::cast::<_, i32>(col_num).unwrap() + offset_col_num; if calc_col_num < 1 { has_error = true; break; - } else { - col_num = calc_col_num as u32; } + col_num = num_traits::cast::<_, u32>(calc_col_num).unwrap(); } if !is_lock_row { - let calc_row_num = row_num as i32 + offset_row_num; + let calc_row_num = + num_traits::cast::<_, i32>(row_num).unwrap() + offset_row_num; if calc_row_num < 1 { has_error = true; break; - } else { - row_num = calc_row_num as u32; } + row_num = num_traits::cast::<_, u32>(calc_row_num).unwrap(); } - let new_corrdinate = coordinate_from_index_with_lock( - &col_num, - &row_num, - &is_lock_col, - &is_lock_row, - ); + let new_corrdinate = + coordinate_from_index_with_lock(col_num, row_num, is_lock_col, is_lock_row); coordinate_list_new.push(new_corrdinate); } else { - coordinate_list_new.push(coordinate.to_string()); + coordinate_list_new.push((*coordinate).to_string()); } } if has_error { @@ -838,57 +920,52 @@ pub fn adjustment_formula_coordinate( } } +#[allow(clippy::too_many_arguments)] pub fn adjustment_insert_formula_coordinate( token_list: &mut [FormulaToken], - root_col_num: &u32, - offset_col_num: &u32, - root_row_num: &u32, - offset_row_num: &u32, + root_col_num: u32, + offset_col_num: u32, + root_row_num: u32, + offset_row_num: u32, worksheet_name: &str, self_worksheet_name: &str, ignore_worksheet: bool, ) -> String { - for token in token_list.into_iter() { + for token in token_list.iter_mut() { if token.get_token_type() == &FormulaTokenTypes::Operand && token.get_token_sub_type() == &FormulaTokenSubTypes::Range { let (sheet_name, range) = split_address(token.get_value()); if ignore_worksheet - || (sheet_name == "" && worksheet_name == self_worksheet_name) + || (sheet_name.is_empty() && worksheet_name == self_worksheet_name) || (sheet_name == worksheet_name) { let mut coordinate_list_new: Vec = Vec::new(); let coordinate_list = get_split_range(range); for coordinate in &coordinate_list { let cell = index_from_coordinate(coordinate); - if cell.0.is_some() { + if cell.0.is_some() && cell.1.is_some() { let mut col_num = cell.0.unwrap(); let mut row_num = cell.1.unwrap(); - let is_lock_col = cell.2.unwrap(); - let is_lock_row = cell.3.unwrap(); + let is_lock_col = cell.2.unwrap_or(false); + let is_lock_row = cell.3.unwrap_or(false); if !is_lock_col { - col_num = adjustment_insert_coordinate( - &col_num, - root_col_num, - offset_col_num, - ); + col_num = + adjustment_insert_coordinate(col_num, root_col_num, offset_col_num); } if !is_lock_row { - row_num = adjustment_insert_coordinate( - &row_num, - root_row_num, - offset_row_num, - ); + row_num = + adjustment_insert_coordinate(row_num, root_row_num, offset_row_num); } let new_corrdinate = coordinate_from_index_with_lock( - &col_num, - &row_num, - &is_lock_col, - &is_lock_row, + col_num, + row_num, + is_lock_col, + is_lock_row, ); coordinate_list_new.push(new_corrdinate); } else { - coordinate_list_new.push(coordinate.to_string()); + coordinate_list_new.push((*coordinate).to_string()); } } let new_value = join_address(sheet_name, &get_join_range(&coordinate_list_new)); @@ -896,26 +973,27 @@ pub fn adjustment_insert_formula_coordinate( } } } - render(token_list.as_ref()) + render(token_list) } +#[allow(clippy::too_many_arguments)] pub fn adjustment_remove_formula_coordinate( token_list: &mut [FormulaToken], - root_col_num: &u32, - offset_col_num: &u32, - root_row_num: &u32, - offset_row_num: &u32, + root_col_num: u32, + offset_col_num: u32, + root_row_num: u32, + offset_row_num: u32, worksheet_name: &str, self_worksheet_name: &str, ignore_worksheet: bool, ) -> String { - for token in token_list.into_iter() { + for token in token_list.iter_mut() { if token.get_token_type() == &FormulaTokenTypes::Operand && token.get_token_sub_type() == &FormulaTokenSubTypes::Range { let (sheet_name, range) = split_address(token.get_value()); if ignore_worksheet - || (sheet_name == "" && worksheet_name == self_worksheet_name) + || (sheet_name.is_empty() && worksheet_name == self_worksheet_name) || (sheet_name == worksheet_name) { let mut coordinate_list_new: Vec = Vec::new(); @@ -928,28 +1006,22 @@ pub fn adjustment_remove_formula_coordinate( let is_lock_col = cell.2.unwrap(); let is_lock_row = cell.3.unwrap(); if !is_lock_col { - col_num = adjustment_remove_coordinate( - &col_num, - root_col_num, - offset_col_num, - ); + col_num = + adjustment_remove_coordinate(col_num, root_col_num, offset_col_num); } if !is_lock_row { - row_num = adjustment_remove_coordinate( - &row_num, - root_row_num, - offset_row_num, - ); + row_num = + adjustment_remove_coordinate(row_num, root_row_num, offset_row_num); } let new_corrdinate = coordinate_from_index_with_lock( - &col_num, - &row_num, - &is_lock_col, - &is_lock_row, + col_num, + row_num, + is_lock_col, + is_lock_row, ); coordinate_list_new.push(new_corrdinate); } else { - coordinate_list_new.push(coordinate.to_string()); + coordinate_list_new.push((*coordinate).to_string()); } } let new_value = join_address(sheet_name, &get_join_range(&coordinate_list_new)); @@ -957,7 +1029,7 @@ pub fn adjustment_remove_formula_coordinate( } } } - render(token_list.as_ref()) + render(token_list) } #[cfg(test)] diff --git a/src/helper/html.rs b/src/helper/html.rs index 031aac65..758cc8a1 100644 --- a/src/helper/html.rs +++ b/src/helper/html.rs @@ -1,13 +1,19 @@ -use crate::structs::Color; -use crate::structs::Font; -use crate::structs::RichText; -use crate::structs::TextElement; -use crate::structs::UnderlineValues; -use crate::structs::VerticalAlignmentRunValues; -use chrono::format; -use html_parser::{Dom, Element, Node}; use std::collections::HashMap; -use thin_vec::ThinVec; + +use html_parser::{ + Dom, + Node, +}; +use phf::phf_map; + +use crate::structs::{ + Color, + Font, + RichText, + TextElement, + UnderlineValues, + VerticalAlignmentRunValues, +}; /// Generate rich text from html. /// # Arguments @@ -51,8 +57,9 @@ pub fn html_to_richtext_custom( Ok(result) } -fn read_node(node_list: &Vec, parent_element: &[HfdElement]) -> ThinVec { - let mut result: ThinVec = ThinVec::new(); +#[allow(clippy::field_reassign_with_default)] +fn read_node(node_list: &Vec, parent_element: &[HfdElement]) -> Vec { + let mut result: Vec = Vec::new(); if node_list.is_empty() { return result; @@ -71,14 +78,14 @@ fn read_node(node_list: &Vec, parent_element: &[HfdElement]) -> ThinVec, parent_element: &[HfdElement]) -> ThinVec, parent_element: &[HfdElement]) -> ThinVec {} + Node::Comment(_) => {} } } - if &data.text != "" { + if !data.text.is_empty() { result.push(data); } result @@ -113,15 +120,15 @@ fn make_rich_text(html_flat_data_list: &[HtmlFlatData], method: &dyn AnalysisMet let mut result = RichText::default(); for html_flat_data in html_flat_data_list { - let mut font_name: Option<&str> = method.font_name(html_flat_data); - let mut size: Option = method.size(html_flat_data); - let mut color: Option = method.color(html_flat_data); - let mut is_bold: bool = method.is_bold(html_flat_data); - let mut is_italic: bool = method.is_italic(html_flat_data); - let mut is_underline: bool = method.is_underline(html_flat_data); - let mut is_superscript: bool = method.is_superscript(html_flat_data); - let mut is_subscript: bool = method.is_subscript(html_flat_data); - let mut is_strikethrough: bool = method.is_strikethrough(html_flat_data); + let font_name: Option<&str> = method.font_name(html_flat_data); + let size: Option = method.size(html_flat_data); + let color: Option = method.color(html_flat_data); + let is_bold: bool = method.is_bold(html_flat_data); + let is_italic: bool = method.is_italic(html_flat_data); + let is_underline: bool = method.is_underline(html_flat_data); + let is_superscript: bool = method.is_superscript(html_flat_data); + let is_subscript: bool = method.is_subscript(html_flat_data); + let is_strikethrough: bool = method.is_strikethrough(html_flat_data); let mut text_element = TextElement::default(); let mut font = Font::default(); @@ -137,7 +144,7 @@ fn make_rich_text(html_flat_data_list: &[HtmlFlatData], method: &dyn AnalysisMet if let Some(v) = color { let argb = v; let mut clr = Color::default(); - clr.set_argb(argb); + clr.set_argb_str(argb); font.set_color(clr); } @@ -148,15 +155,14 @@ fn make_rich_text(html_flat_data_list: &[HtmlFlatData], method: &dyn AnalysisMet font.set_italic(is_italic); } if is_underline { - font.get_font_underline_mut() - .set_val(UnderlineValues::Single); + font.font_underline_mut().set_val(UnderlineValues::Single); } if is_superscript { - font.get_vertical_text_alignment_mut() + font.vertical_text_alignment_mut() .set_val(VerticalAlignmentRunValues::Superscript); } if is_subscript { - font.get_vertical_text_alignment_mut() + font.vertical_text_alignment_mut() .set_val(VerticalAlignmentRunValues::Subscript); } if is_strikethrough { @@ -172,31 +178,34 @@ fn make_rich_text(html_flat_data_list: &[HtmlFlatData], method: &dyn AnalysisMet #[derive(Clone, Default, Debug)] pub struct HtmlFlatData { - text: String, - element: ThinVec, + text: String, + element: Vec, } #[derive(Clone, Default, Debug)] pub struct HfdElement { - name: String, + name: String, attributes: HashMap, - classes: ThinVec, + classes: Vec, } impl HfdElement { #[inline] + #[must_use] pub fn has_name(&self, name: &str) -> bool { self.name == name } #[inline] + #[must_use] pub fn get_by_name_and_attribute(&self, name: &str, attribute: &str) -> Option<&str> { self.attributes .get(attribute) - .and_then(|v| (self.name == name).then(|| v)) - .map(|x| x.as_str()) + .and_then(|v| (self.name == name).then_some(v)) + .map(String::as_str) } #[inline] + #[must_use] pub fn contains_class(&self, class: &str) -> bool { self.classes.contains(&class.to_string()) } @@ -219,7 +228,7 @@ pub trait AnalysisMethod { struct DataAnalysis {} impl AnalysisMethod for DataAnalysis { #[inline] - fn font_name<'a>(&'a self, html_flat_data: &'a HtmlFlatData) -> Option<&str> { + fn font_name<'a>(&'a self, html_flat_data: &'a HtmlFlatData) -> Option<&'a str> { html_flat_data .element .iter() @@ -236,19 +245,15 @@ impl AnalysisMethod for DataAnalysis { } fn color(&self, html_flat_data: &HtmlFlatData) -> Option { - let mut result: Option = None; html_flat_data .element .iter() - .flat_map(|element| element.get_by_name_and_attribute("font", "color")) + .filter_map(|element| element.get_by_name_and_attribute("font", "color")) .find_map(|v| { let color = v.trim_start_matches('#').to_uppercase(); COLOR_MAP - .iter() - .find_map(|(key, value)| { - (*key.to_uppercase() == color).then(|| value.to_uppercase()) - }) - .or_else(|| Some(color)) + .get(&color) + .map_or(Some(color), |v| Some((*v).to_string())) }) } @@ -291,528 +296,528 @@ impl AnalysisMethod for DataAnalysis { } } -const COLOR_MAP: &[(&str, &str)] = &[ - ("aliceblue", "f0f8ff"), - ("antiquewhite", "faebd7"), - ("antiquewhite1", "ffefdb"), - ("antiquewhite2", "eedfcc"), - ("antiquewhite3", "cdc0b0"), - ("antiquewhite4", "8b8378"), - ("aqua", "00ffff"), - ("aquamarine1", "7fffd4"), - ("aquamarine2", "76eec6"), - ("aquamarine4", "458b74"), - ("azure1", "f0ffff"), - ("azure2", "e0eeee"), - ("azure3", "c1cdcd"), - ("azure4", "838b8b"), - ("beige", "f5f5dc"), - ("bisque1", "ffe4c4"), - ("bisque2", "eed5b7"), - ("bisque3", "cdb79e"), - ("bisque4", "8b7d6b"), - ("black", "000000"), - ("blanchedalmond", "ffebcd"), - ("blue", "0000ff"), - ("blue1", "0000ff"), - ("blue2", "0000ee"), - ("blue4", "00008b"), - ("blueviolet", "8a2be2"), - ("brown", "a52a2a"), - ("brown1", "ff4040"), - ("brown2", "ee3b3b"), - ("brown3", "cd3333"), - ("brown4", "8b2323"), - ("burlywood", "deb887"), - ("burlywood1", "ffd39b"), - ("burlywood2", "eec591"), - ("burlywood3", "cdaa7d"), - ("burlywood4", "8b7355"), - ("cadetblue", "5f9ea0"), - ("cadetblue1", "98f5ff"), - ("cadetblue2", "8ee5ee"), - ("cadetblue3", "7ac5cd"), - ("cadetblue4", "53868b"), - ("chartreuse1", "7fff00"), - ("chartreuse2", "76ee00"), - ("chartreuse3", "66cd00"), - ("chartreuse4", "458b00"), - ("chocolate", "d2691e"), - ("chocolate1", "ff7f24"), - ("chocolate2", "ee7621"), - ("chocolate3", "cd661d"), - ("coral", "ff7f50"), - ("coral1", "ff7256"), - ("coral2", "ee6a50"), - ("coral3", "cd5b45"), - ("coral4", "8b3e2f"), - ("cornflowerblue", "6495ed"), - ("cornsilk1", "fff8dc"), - ("cornsilk2", "eee8cd"), - ("cornsilk3", "cdc8b1"), - ("cornsilk4", "8b8878"), - ("cyan1", "00ffff"), - ("cyan2", "00eeee"), - ("cyan3", "00cdcd"), - ("cyan4", "008b8b"), - ("darkgoldenrod", "b8860b"), - ("darkgoldenrod1", "ffb90f"), - ("darkgoldenrod2", "eead0e"), - ("darkgoldenrod3", "cd950c"), - ("darkgoldenrod4", "8b6508"), - ("darkgreen", "006400"), - ("darkkhaki", "bdb76b"), - ("darkolivegreen", "556b2f"), - ("darkolivegreen1", "caff70"), - ("darkolivegreen2", "bcee68"), - ("darkolivegreen3", "a2cd5a"), - ("darkolivegreen4", "6e8b3d"), - ("darkorange", "ff8c00"), - ("darkorange1", "ff7f00"), - ("darkorange2", "ee7600"), - ("darkorange3", "cd6600"), - ("darkorange4", "8b4500"), - ("darkorchid", "9932cc"), - ("darkorchid1", "bf3eff"), - ("darkorchid2", "b23aee"), - ("darkorchid3", "9a32cd"), - ("darkorchid4", "68228b"), - ("darksalmon", "e9967a"), - ("darkseagreen", "8fbc8f"), - ("darkseagreen1", "c1ffc1"), - ("darkseagreen2", "b4eeb4"), - ("darkseagreen3", "9bcd9b"), - ("darkseagreen4", "698b69"), - ("darkslateblue", "483d8b"), - ("darkslategray", "2f4f4f"), - ("darkslategray1", "97ffff"), - ("darkslategray2", "8deeee"), - ("darkslategray3", "79cdcd"), - ("darkslategray4", "528b8b"), - ("darkturquoise", "00ced1"), - ("darkviolet", "9400d3"), - ("deeppink1", "ff1493"), - ("deeppink2", "ee1289"), - ("deeppink3", "cd1076"), - ("deeppink4", "8b0a50"), - ("deepskyblue1", "00bfff"), - ("deepskyblue2", "00b2ee"), - ("deepskyblue3", "009acd"), - ("deepskyblue4", "00688b"), - ("dimgray", "696969"), - ("dodgerblue1", "1e90ff"), - ("dodgerblue2", "1c86ee"), - ("dodgerblue3", "1874cd"), - ("dodgerblue4", "104e8b"), - ("firebrick", "b22222"), - ("firebrick1", "ff3030"), - ("firebrick2", "ee2c2c"), - ("firebrick3", "cd2626"), - ("firebrick4", "8b1a1a"), - ("floralwhite", "fffaf0"), - ("forestgreen", "228b22"), - ("fuchsia", "ff00ff"), - ("gainsboro", "dcdcdc"), - ("ghostwhite", "f8f8ff"), - ("gold1", "ffd700"), - ("gold2", "eec900"), - ("gold3", "cdad00"), - ("gold4", "8b7500"), - ("goldenrod", "daa520"), - ("goldenrod1", "ffc125"), - ("goldenrod2", "eeb422"), - ("goldenrod3", "cd9b1d"), - ("goldenrod4", "8b6914"), - ("gray", "bebebe"), - ("gray1", "030303"), - ("gray10", "1a1a1a"), - ("gray11", "1c1c1c"), - ("gray12", "1f1f1f"), - ("gray13", "212121"), - ("gray14", "242424"), - ("gray15", "262626"), - ("gray16", "292929"), - ("gray17", "2b2b2b"), - ("gray18", "2e2e2e"), - ("gray19", "303030"), - ("gray2", "050505"), - ("gray20", "333333"), - ("gray21", "363636"), - ("gray22", "383838"), - ("gray23", "3b3b3b"), - ("gray24", "3d3d3d"), - ("gray25", "404040"), - ("gray26", "424242"), - ("gray27", "454545"), - ("gray28", "474747"), - ("gray29", "4a4a4a"), - ("gray3", "080808"), - ("gray30", "4d4d4d"), - ("gray31", "4f4f4f"), - ("gray32", "525252"), - ("gray33", "545454"), - ("gray34", "575757"), - ("gray35", "595959"), - ("gray36", "5c5c5c"), - ("gray37", "5e5e5e"), - ("gray38", "616161"), - ("gray39", "636363"), - ("gray4", "0a0a0a"), - ("gray40", "666666"), - ("gray41", "696969"), - ("gray42", "6b6b6b"), - ("gray43", "6e6e6e"), - ("gray44", "707070"), - ("gray45", "737373"), - ("gray46", "757575"), - ("gray47", "787878"), - ("gray48", "7a7a7a"), - ("gray49", "7d7d7d"), - ("gray5", "0d0d0d"), - ("gray50", "7f7f7f"), - ("gray51", "828282"), - ("gray52", "858585"), - ("gray53", "878787"), - ("gray54", "8a8a8a"), - ("gray55", "8c8c8c"), - ("gray56", "8f8f8f"), - ("gray57", "919191"), - ("gray58", "949494"), - ("gray59", "969696"), - ("gray6", "0f0f0f"), - ("gray60", "999999"), - ("gray61", "9c9c9c"), - ("gray62", "9e9e9e"), - ("gray63", "a1a1a1"), - ("gray64", "a3a3a3"), - ("gray65", "a6a6a6"), - ("gray66", "a8a8a8"), - ("gray67", "ababab"), - ("gray68", "adadad"), - ("gray69", "b0b0b0"), - ("gray7", "121212"), - ("gray70", "b3b3b3"), - ("gray71", "b5b5b5"), - ("gray72", "b8b8b8"), - ("gray73", "bababa"), - ("gray74", "bdbdbd"), - ("gray75", "bfbfbf"), - ("gray76", "c2c2c2"), - ("gray77", "c4c4c4"), - ("gray78", "c7c7c7"), - ("gray79", "c9c9c9"), - ("gray8", "141414"), - ("gray80", "cccccc"), - ("gray81", "cfcfcf"), - ("gray82", "d1d1d1"), - ("gray83", "d4d4d4"), - ("gray84", "d6d6d6"), - ("gray85", "d9d9d9"), - ("gray86", "dbdbdb"), - ("gray87", "dedede"), - ("gray88", "e0e0e0"), - ("gray89", "e3e3e3"), - ("gray9", "171717"), - ("gray90", "e5e5e5"), - ("gray91", "e8e8e8"), - ("gray92", "ebebeb"), - ("gray93", "ededed"), - ("gray94", "f0f0f0"), - ("gray95", "f2f2f2"), - ("gray97", "f7f7f7"), - ("gray98", "fafafa"), - ("gray99", "fcfcfc"), - ("green", "00ff00"), - ("green1", "00ff00"), - ("green2", "00ee00"), - ("green3", "00cd00"), - ("green4", "008b00"), - ("greenyellow", "adff2f"), - ("honeydew1", "f0fff0"), - ("honeydew2", "e0eee0"), - ("honeydew3", "c1cdc1"), - ("honeydew4", "838b83"), - ("hotpink", "ff69b4"), - ("hotpink1", "ff6eb4"), - ("hotpink2", "ee6aa7"), - ("hotpink3", "cd6090"), - ("hotpink4", "8b3a62"), - ("indianred", "cd5c5c"), - ("indianred1", "ff6a6a"), - ("indianred2", "ee6363"), - ("indianred3", "cd5555"), - ("indianred4", "8b3a3a"), - ("ivory1", "fffff0"), - ("ivory2", "eeeee0"), - ("ivory3", "cdcdc1"), - ("ivory4", "8b8b83"), - ("khaki", "f0e68c"), - ("khaki1", "fff68f"), - ("khaki2", "eee685"), - ("khaki3", "cdc673"), - ("khaki4", "8b864e"), - ("lavender", "e6e6fa"), - ("lavenderblush1", "fff0f5"), - ("lavenderblush2", "eee0e5"), - ("lavenderblush3", "cdc1c5"), - ("lavenderblush4", "8b8386"), - ("lawngreen", "7cfc00"), - ("lemonchiffon1", "fffacd"), - ("lemonchiffon2", "eee9bf"), - ("lemonchiffon3", "cdc9a5"), - ("lemonchiffon4", "8b8970"), - ("light", "eedd82"), - ("lightblue", "add8e6"), - ("lightblue1", "bfefff"), - ("lightblue2", "b2dfee"), - ("lightblue3", "9ac0cd"), - ("lightblue4", "68838b"), - ("lightcoral", "f08080"), - ("lightcyan1", "e0ffff"), - ("lightcyan2", "d1eeee"), - ("lightcyan3", "b4cdcd"), - ("lightcyan4", "7a8b8b"), - ("lightgoldenrod1", "ffec8b"), - ("lightgoldenrod2", "eedc82"), - ("lightgoldenrod3", "cdbe70"), - ("lightgoldenrod4", "8b814c"), - ("lightgoldenrodyellow", "fafad2"), - ("lightgray", "d3d3d3"), - ("lightpink", "ffb6c1"), - ("lightpink1", "ffaeb9"), - ("lightpink2", "eea2ad"), - ("lightpink3", "cd8c95"), - ("lightpink4", "8b5f65"), - ("lightsalmon1", "ffa07a"), - ("lightsalmon2", "ee9572"), - ("lightsalmon3", "cd8162"), - ("lightsalmon4", "8b5742"), - ("lightseagreen", "20b2aa"), - ("lightskyblue", "87cefa"), - ("lightskyblue1", "b0e2ff"), - ("lightskyblue2", "a4d3ee"), - ("lightskyblue3", "8db6cd"), - ("lightskyblue4", "607b8b"), - ("lightslateblue", "8470ff"), - ("lightslategray", "778899"), - ("lightsteelblue", "b0c4de"), - ("lightsteelblue1", "cae1ff"), - ("lightsteelblue2", "bcd2ee"), - ("lightsteelblue3", "a2b5cd"), - ("lightsteelblue4", "6e7b8b"), - ("lightyellow1", "ffffe0"), - ("lightyellow2", "eeeed1"), - ("lightyellow3", "cdcdb4"), - ("lightyellow4", "8b8b7a"), - ("lime", "00ff00"), - ("limegreen", "32cd32"), - ("linen", "faf0e6"), - ("magenta", "ff00ff"), - ("magenta2", "ee00ee"), - ("magenta3", "cd00cd"), - ("magenta4", "8b008b"), - ("maroon", "b03060"), - ("maroon1", "ff34b3"), - ("maroon2", "ee30a7"), - ("maroon3", "cd2990"), - ("maroon4", "8b1c62"), - ("medium", "66cdaa"), - ("mediumaquamarine", "66cdaa"), - ("mediumblue", "0000cd"), - ("mediumorchid", "ba55d3"), - ("mediumorchid1", "e066ff"), - ("mediumorchid2", "d15fee"), - ("mediumorchid3", "b452cd"), - ("mediumorchid4", "7a378b"), - ("mediumpurple", "9370db"), - ("mediumpurple1", "ab82ff"), - ("mediumpurple2", "9f79ee"), - ("mediumpurple3", "8968cd"), - ("mediumpurple4", "5d478b"), - ("mediumseagreen", "3cb371"), - ("mediumslateblue", "7b68ee"), - ("mediumspringgreen", "00fa9a"), - ("mediumturquoise", "48d1cc"), - ("mediumvioletred", "c71585"), - ("midnightblue", "191970"), - ("mintcream", "f5fffa"), - ("mistyrose1", "ffe4e1"), - ("mistyrose2", "eed5d2"), - ("mistyrose3", "cdb7b5"), - ("mistyrose4", "8b7d7b"), - ("moccasin", "ffe4b5"), - ("navajowhite1", "ffdead"), - ("navajowhite2", "eecfa1"), - ("navajowhite3", "cdb38b"), - ("navajowhite4", "8b795e"), - ("navy", "000080"), - ("navyblue", "000080"), - ("oldlace", "fdf5e6"), - ("olive", "808000"), - ("olivedrab", "6b8e23"), - ("olivedrab1", "c0ff3e"), - ("olivedrab2", "b3ee3a"), - ("olivedrab4", "698b22"), - ("orange", "ffa500"), - ("orange1", "ffa500"), - ("orange2", "ee9a00"), - ("orange3", "cd8500"), - ("orange4", "8b5a00"), - ("orangered1", "ff4500"), - ("orangered2", "ee4000"), - ("orangered3", "cd3700"), - ("orangered4", "8b2500"), - ("orchid", "da70d6"), - ("orchid1", "ff83fa"), - ("orchid2", "ee7ae9"), - ("orchid3", "cd69c9"), - ("orchid4", "8b4789"), - ("pale", "db7093"), - ("palegoldenrod", "eee8aa"), - ("palegreen", "98fb98"), - ("palegreen1", "9aff9a"), - ("palegreen2", "90ee90"), - ("palegreen3", "7ccd7c"), - ("palegreen4", "548b54"), - ("paleturquoise", "afeeee"), - ("paleturquoise1", "bbffff"), - ("paleturquoise2", "aeeeee"), - ("paleturquoise3", "96cdcd"), - ("paleturquoise4", "668b8b"), - ("palevioletred", "db7093"), - ("palevioletred1", "ff82ab"), - ("palevioletred2", "ee799f"), - ("palevioletred3", "cd6889"), - ("palevioletred4", "8b475d"), - ("papayawhip", "ffefd5"), - ("peachpuff1", "ffdab9"), - ("peachpuff2", "eecbad"), - ("peachpuff3", "cdaf95"), - ("peachpuff4", "8b7765"), - ("pink", "ffc0cb"), - ("pink1", "ffb5c5"), - ("pink2", "eea9b8"), - ("pink3", "cd919e"), - ("pink4", "8b636c"), - ("plum", "dda0dd"), - ("plum1", "ffbbff"), - ("plum2", "eeaeee"), - ("plum3", "cd96cd"), - ("plum4", "8b668b"), - ("powderblue", "b0e0e6"), - ("purple", "a020f0"), - ("rebeccapurple", "663399"), - ("purple1", "9b30ff"), - ("purple2", "912cee"), - ("purple3", "7d26cd"), - ("purple4", "551a8b"), - ("red", "ff0000"), - ("red1", "ff0000"), - ("red2", "ee0000"), - ("red3", "cd0000"), - ("red4", "8b0000"), - ("rosybrown", "bc8f8f"), - ("rosybrown1", "ffc1c1"), - ("rosybrown2", "eeb4b4"), - ("rosybrown3", "cd9b9b"), - ("rosybrown4", "8b6969"), - ("royalblue", "4169e1"), - ("royalblue1", "4876ff"), - ("royalblue2", "436eee"), - ("royalblue3", "3a5fcd"), - ("royalblue4", "27408b"), - ("saddlebrown", "8b4513"), - ("salmon", "fa8072"), - ("salmon1", "ff8c69"), - ("salmon2", "ee8262"), - ("salmon3", "cd7054"), - ("salmon4", "8b4c39"), - ("sandybrown", "f4a460"), - ("seagreen1", "54ff9f"), - ("seagreen2", "4eee94"), - ("seagreen3", "43cd80"), - ("seagreen4", "2e8b57"), - ("seashell1", "fff5ee"), - ("seashell2", "eee5de"), - ("seashell3", "cdc5bf"), - ("seashell4", "8b8682"), - ("sienna", "a0522d"), - ("sienna1", "ff8247"), - ("sienna2", "ee7942"), - ("sienna3", "cd6839"), - ("sienna4", "8b4726"), - ("silver", "c0c0c0"), - ("skyblue", "87ceeb"), - ("skyblue1", "87ceff"), - ("skyblue2", "7ec0ee"), - ("skyblue3", "6ca6cd"), - ("skyblue4", "4a708b"), - ("slateblue", "6a5acd"), - ("slateblue1", "836fff"), - ("slateblue2", "7a67ee"), - ("slateblue3", "6959cd"), - ("slateblue4", "473c8b"), - ("slategray", "708090"), - ("slategray1", "c6e2ff"), - ("slategray2", "b9d3ee"), - ("slategray3", "9fb6cd"), - ("slategray4", "6c7b8b"), - ("snow1", "fffafa"), - ("snow2", "eee9e9"), - ("snow3", "cdc9c9"), - ("snow4", "8b8989"), - ("springgreen1", "00ff7f"), - ("springgreen2", "00ee76"), - ("springgreen3", "00cd66"), - ("springgreen4", "008b45"), - ("steelblue", "4682b4"), - ("steelblue1", "63b8ff"), - ("steelblue2", "5cacee"), - ("steelblue3", "4f94cd"), - ("steelblue4", "36648b"), - ("tan", "d2b48c"), - ("tan1", "ffa54f"), - ("tan2", "ee9a49"), - ("tan3", "cd853f"), - ("tan4", "8b5a2b"), - ("teal", "008080"), - ("thistle", "d8bfd8"), - ("thistle1", "ffe1ff"), - ("thistle2", "eed2ee"), - ("thistle3", "cdb5cd"), - ("thistle4", "8b7b8b"), - ("tomato1", "ff6347"), - ("tomato2", "ee5c42"), - ("tomato3", "cd4f39"), - ("tomato4", "8b3626"), - ("turquoise", "40e0d0"), - ("turquoise1", "00f5ff"), - ("turquoise2", "00e5ee"), - ("turquoise3", "00c5cd"), - ("turquoise4", "00868b"), - ("violet", "ee82ee"), - ("violetred", "d02090"), - ("violetred1", "ff3e96"), - ("violetred2", "ee3a8c"), - ("violetred3", "cd3278"), - ("violetred4", "8b2252"), - ("wheat", "f5deb3"), - ("wheat1", "ffe7ba"), - ("wheat2", "eed8ae"), - ("wheat3", "cdba96"), - ("wheat4", "8b7e66"), - ("white", "ffffff"), - ("whitesmoke", "f5f5f5"), - ("yellow", "ffff00"), - ("yellow1", "ffff00"), - ("yellow2", "eeee00"), - ("yellow3", "cdcd00"), - ("yellow4", "8b8b00"), - ("yellowgreen", "9acd32"), -]; +static COLOR_MAP: phf::Map<&str, &str> = phf_map! { + "ALICEBLUE" => "F0F8FF", + "ANTIQUEWHITE" => "FAEBD7", + "ANTIQUEWHITE1" => "FFEFDB", + "ANTIQUEWHITE2" => "EEDFCC", + "ANTIQUEWHITE3" => "CDC0B0", + "ANTIQUEWHITE4" => "8B8378", + "AQUA" => "00FFFF", + "AQUAMARINE1" => "7FFFD4", + "AQUAMARINE2" => "76EEC6", + "AQUAMARINE4" => "458B74", + "AZURE1" => "F0FFFF", + "AZURE2" => "E0EEEE", + "AZURE3" => "C1CDCD", + "AZURE4" => "838B8B", + "BEIGE" => "F5F5DC", + "BISQUE1" => "FFE4C4", + "BISQUE2" => "EED5B7", + "BISQUE3" => "CDB79E", + "BISQUE4" => "8B7D6B", + "BLACK" => "000000", + "BLANCHEDALMOND" => "FFEBCD", + "BLUE" => "0000FF", + "BLUE1" => "0000FF", + "BLUE2" => "0000EE", + "BLUE4" => "00008B", + "BLUEVIOLET" => "8A2BE2", + "BROWN" => "A52A2A", + "BROWN1" => "FF4040", + "BROWN2" => "EE3B3B", + "BROWN3" => "CD3333", + "BROWN4" => "8B2323", + "BURLYWOOD" => "DEB887", + "BURLYWOOD1" => "FFD39B", + "BURLYWOOD2" => "EEC591", + "BURLYWOOD3" => "CDAA7D", + "BURLYWOOD4" => "8B7355", + "CADETBLUE" => "5F9EA0", + "CADETBLUE1" => "98F5FF", + "CADETBLUE2" => "8EE5EE", + "CADETBLUE3" => "7AC5CD", + "CADETBLUE4" => "53868B", + "CHARTREUSE1" => "7FFF00", + "CHARTREUSE2" => "76EE00", + "CHARTREUSE3" => "66CD00", + "CHARTREUSE4" => "458B00", + "CHOCOLATE" => "D2691E", + "CHOCOLATE1" => "FF7F24", + "CHOCOLATE2" => "EE7621", + "CHOCOLATE3" => "CD661D", + "CORAL" => "FF7F50", + "CORAL1" => "FF7256", + "CORAL2" => "EE6A50", + "CORAL3" => "CD5B45", + "CORAL4" => "8B3E2F", + "CORNFLOWERBLUE" => "6495ED", + "CORNSILK1" => "FFF8DC", + "CORNSILK2" => "EEE8CD", + "CORNSILK3" => "CDC8B1", + "CORNSILK4" => "8B8878", + "CYAN1" => "00FFFF", + "CYAN2" => "00EEEE", + "CYAN3" => "00CDCD", + "CYAN4" => "008B8B", + "DARKGOLDENROD" => "B8860B", + "DARKGOLDENROD1" => "FFB90F", + "DARKGOLDENROD2" => "EEAD0E", + "DARKGOLDENROD3" => "CD950C", + "DARKGOLDENROD4" => "8B6508", + "DARKGREEN" => "006400", + "DARKKHAKI" => "BDB76B", + "DARKOLIVEGREEN" => "556B2F", + "DARKOLIVEGREEN1" => "CAFF70", + "DARKOLIVEGREEN2" => "BCEE68", + "DARKOLIVEGREEN3" => "A2CD5A", + "DARKOLIVEGREEN4" => "6E8B3D", + "DARKORANGE" => "FF8C00", + "DARKORANGE1" => "FF7F00", + "DARKORANGE2" => "EE7600", + "DARKORANGE3" => "CD6600", + "DARKORANGE4" => "8B4500", + "DARKORCHID" => "9932CC", + "DARKORCHID1" => "BF3EFF", + "DARKORCHID2" => "B23AEE", + "DARKORCHID3" => "9A32CD", + "DARKORCHID4" => "68228B", + "DARKSALMON" => "E9967A", + "DARKSEAGREEN" => "8FBC8F", + "DARKSEAGREEN1" => "C1FFC1", + "DARKSEAGREEN2" => "B4EEB4", + "DARKSEAGREEN3" => "9BCD9B", + "DARKSEAGREEN4" => "698B69", + "DARKSLATEBLUE" => "483D8B", + "DARKSLATEGRAY" => "2F4F4F", + "DARKSLATEGRAY1" => "97FFFF", + "DARKSLATEGRAY2" => "8DEEEE", + "DARKSLATEGRAY3" => "79CDCD", + "DARKSLATEGRAY4" => "528B8B", + "DARKTURQUOISE" => "00CED1", + "DARKVIOLET" => "9400D3", + "DEEPPINK1" => "FF1493", + "DEEPPINK2" => "EE1289", + "DEEPPINK3" => "CD1076", + "DEEPPINK4" => "8B0A50", + "DEEPSKYBLUE1" => "00BFFF", + "DEEPSKYBLUE2" => "00B2EE", + "DEEPSKYBLUE3" => "009ACD", + "DEEPSKYBLUE4" => "00688B", + "DIMGRAY" => "696969", + "DODGERBLUE1" => "1E90FF", + "DODGERBLUE2" => "1C86EE", + "DODGERBLUE3" => "1874CD", + "DODGERBLUE4" => "104E8B", + "FIREBRICK" => "B22222", + "FIREBRICK1" => "FF3030", + "FIREBRICK2" => "EE2C2C", + "FIREBRICK3" => "CD2626", + "FIREBRICK4" => "8B1A1A", + "FLORALWHITE" => "FFFAF0", + "FORESTGREEN" => "228B22", + "FUCHSIA" => "FF00FF", + "GAINSBORO" => "DCDCDC", + "GHOSTWHITE" => "F8F8FF", + "GOLD1" => "FFD700", + "GOLD2" => "EEC900", + "GOLD3" => "CDAD00", + "GOLD4" => "8B7500", + "GOLDENROD" => "DAA520", + "GOLDENROD1" => "FFC125", + "GOLDENROD2" => "EEB422", + "GOLDENROD3" => "CD9B1D", + "GOLDENROD4" => "8B6914", + "GRAY" => "BEBEBE", + "GRAY1" => "030303", + "GRAY10" => "1A1A1A", + "GRAY11" => "1C1C1C", + "GRAY12" => "1F1F1F", + "GRAY13" => "212121", + "GRAY14" => "242424", + "GRAY15" => "262626", + "GRAY16" => "292929", + "GRAY17" => "2B2B2B", + "GRAY18" => "2E2E2E", + "GRAY19" => "303030", + "GRAY2" => "050505", + "GRAY20" => "333333", + "GRAY21" => "363636", + "GRAY22" => "383838", + "GRAY23" => "3B3B3B", + "GRAY24" => "3D3D3D", + "GRAY25" => "404040", + "GRAY26" => "424242", + "GRAY27" => "454545", + "GRAY28" => "474747", + "GRAY29" => "4A4A4A", + "GRAY3" => "080808", + "GRAY30" => "4D4D4D", + "GRAY31" => "4F4F4F", + "GRAY32" => "525252", + "GRAY33" => "545454", + "GRAY34" => "575757", + "GRAY35" => "595959", + "GRAY36" => "5C5C5C", + "GRAY37" => "5E5E5E", + "GRAY38" => "616161", + "GRAY39" => "636363", + "GRAY4" => "0A0A0A", + "GRAY40" => "666666", + "GRAY41" => "696969", + "GRAY42" => "6B6B6B", + "GRAY43" => "6E6E6E", + "GRAY44" => "707070", + "GRAY45" => "737373", + "GRAY46" => "757575", + "GRAY47" => "787878", + "GRAY48" => "7A7A7A", + "GRAY49" => "7D7D7D", + "GRAY5" => "0D0D0D", + "GRAY50" => "7F7F7F", + "GRAY51" => "828282", + "GRAY52" => "858585", + "GRAY53" => "878787", + "GRAY54" => "8A8A8A", + "GRAY55" => "8C8C8C", + "GRAY56" => "8F8F8F", + "GRAY57" => "919191", + "GRAY58" => "949494", + "GRAY59" => "969696", + "GRAY6" => "0F0F0F", + "GRAY60" => "999999", + "GRAY61" => "9C9C9C", + "GRAY62" => "9E9E9E", + "GRAY63" => "A1A1A1", + "GRAY64" => "A3A3A3", + "GRAY65" => "A6A6A6", + "GRAY66" => "A8A8A8", + "GRAY67" => "ABABAB", + "GRAY68" => "ADADAD", + "GRAY69" => "B0B0B0", + "GRAY7" => "121212", + "GRAY70" => "B3B3B3", + "GRAY71" => "B5B5B5", + "GRAY72" => "B8B8B8", + "GRAY73" => "BABABA", + "GRAY74" => "BDBDBD", + "GRAY75" => "BFBFBF", + "GRAY76" => "C2C2C2", + "GRAY77" => "C4C4C4", + "GRAY78" => "C7C7C7", + "GRAY79" => "C9C9C9", + "GRAY8" => "141414", + "GRAY80" => "CCCCCC", + "GRAY81" => "CFCFCF", + "GRAY82" => "D1D1D1", + "GRAY83" => "D4D4D4", + "GRAY84" => "D6D6D6", + "GRAY85" => "D9D9D9", + "GRAY86" => "DBDBDB", + "GRAY87" => "DEDEDE", + "GRAY88" => "E0E0E0", + "GRAY89" => "E3E3E3", + "GRAY9" => "171717", + "GRAY90" => "E5E5E5", + "GRAY91" => "E8E8E8", + "GRAY92" => "EBEBEB", + "GRAY93" => "EDEDED", + "GRAY94" => "F0F0F0", + "GRAY95" => "F2F2F2", + "GRAY97" => "F7F7F7", + "GRAY98" => "FAFAFA", + "GRAY99" => "FCFCFC", + "GREEN" => "00FF00", + "GREEN1" => "00FF00", + "GREEN2" => "00EE00", + "GREEN3" => "00CD00", + "GREEN4" => "008B00", + "GREENYELLOW" => "ADFF2F", + "HONEYDEW1" => "F0FFF0", + "HONEYDEW2" => "E0EEE0", + "HONEYDEW3" => "C1CDC1", + "HONEYDEW4" => "838B83", + "HOTPINK" => "FF69B4", + "HOTPINK1" => "FF6EB4", + "HOTPINK2" => "EE6AA7", + "HOTPINK3" => "CD6090", + "HOTPINK4" => "8B3A62", + "INDIANRED" => "CD5C5C", + "INDIANRED1" => "FF6A6A", + "INDIANRED2" => "EE6363", + "INDIANRED3" => "CD5555", + "INDIANRED4" => "8B3A3A", + "IVORY1" => "FFFFF0", + "IVORY2" => "EEEEE0", + "IVORY3" => "CDCDC1", + "IVORY4" => "8B8B83", + "KHAKI" => "F0E68C", + "KHAKI1" => "FFF68F", + "KHAKI2" => "EEE685", + "KHAKI3" => "CDC673", + "KHAKI4" => "8B864E", + "LAVENDER" => "E6E6FA", + "LAVENDERBLUSH1" => "FFF0F5", + "LAVENDERBLUSH2" => "EEE0E5", + "LAVENDERBLUSH3" => "CDC1C5", + "LAVENDERBLUSH4" => "8B8386", + "LAWNGREEN" => "7CFC00", + "LEMONCHIFFON1" => "FFFACD", + "LEMONCHIFFON2" => "EEE9BF", + "LEMONCHIFFON3" => "CDC9A5", + "LEMONCHIFFON4" => "8B8970", + "LIGHT" => "EEDD82", + "LIGHTBLUE" => "ADD8E6", + "LIGHTBLUE1" => "BFEFFF", + "LIGHTBLUE2" => "B2DFEE", + "LIGHTBLUE3" => "9AC0CD", + "LIGHTBLUE4" => "68838B", + "LIGHTCORAL" => "F08080", + "LIGHTCYAN1" => "E0FFFF", + "LIGHTCYAN2" => "D1EEEE", + "LIGHTCYAN3" => "B4CDCD", + "LIGHTCYAN4" => "7A8B8B", + "LIGHTGOLDENROD1" => "FFEC8B", + "LIGHTGOLDENROD2" => "EEDC82", + "LIGHTGOLDENROD3" => "CDBE70", + "LIGHTGOLDENROD4" => "8B814C", + "LIGHTGOLDENRODYELLOW" => "FAFAD2", + "LIGHTGRAY" => "D3D3D3", + "LIGHTPINK" => "FFB6C1", + "LIGHTPINK1" => "FFAEB9", + "LIGHTPINK2" => "EEA2AD", + "LIGHTPINK3" => "CD8C95", + "LIGHTPINK4" => "8B5F65", + "LIGHTSALMON1" => "FFA07A", + "LIGHTSALMON2" => "EE9572", + "LIGHTSALMON3" => "CD8162", + "LIGHTSALMON4" => "8B5742", + "LIGHTSEAGREEN" => "20B2AA", + "LIGHTSKYBLUE" => "87CEFA", + "LIGHTSKYBLUE1" => "B0E2FF", + "LIGHTSKYBLUE2" => "A4D3EE", + "LIGHTSKYBLUE3" => "8DB6CD", + "LIGHTSKYBLUE4" => "607B8B", + "LIGHTSLATEBLUE" => "8470FF", + "LIGHTSLATEGRAY" => "778899", + "LIGHTSTEELBLUE" => "B0C4DE", + "LIGHTSTEELBLUE1" => "CAE1FF", + "LIGHTSTEELBLUE2" => "BCD2EE", + "LIGHTSTEELBLUE3" => "A2B5CD", + "LIGHTSTEELBLUE4" => "6E7B8B", + "LIGHTYELLOW1" => "FFFFE0", + "LIGHTYELLOW2" => "EEEED1", + "LIGHTYELLOW3" => "CDCDB4", + "LIGHTYELLOW4" => "8B8B7A", + "LIME" => "00FF00", + "LIMEGREEN" => "32CD32", + "LINEN" => "FAF0E6", + "MAGENTA" => "FF00FF", + "MAGENTA2" => "EE00EE", + "MAGENTA3" => "CD00CD", + "MAGENTA4" => "8B008B", + "MAROON" => "B03060", + "MAROON1" => "FF34B3", + "MAROON2" => "EE30A7", + "MAROON3" => "CD2990", + "MAROON4" => "8B1C62", + "MEDIUM" => "66CDAA", + "MEDIUMAQUAMARINE" => "66CDAA", + "MEDIUMBLUE" => "0000CD", + "MEDIUMORCHID" => "BA55D3", + "MEDIUMORCHID1" => "E066FF", + "MEDIUMORCHID2" => "D15FEE", + "MEDIUMORCHID3" => "B452CD", + "MEDIUMORCHID4" => "7A378B", + "MEDIUMPURPLE" => "9370DB", + "MEDIUMPURPLE1" => "AB82FF", + "MEDIUMPURPLE2" => "9F79EE", + "MEDIUMPURPLE3" => "8968CD", + "MEDIUMPURPLE4" => "5D478B", + "MEDIUMSEAGREEN" => "3CB371", + "MEDIUMSLATEBLUE" => "7B68EE", + "MEDIUMSPRINGGREEN" => "00FA9A", + "MEDIUMTURQUOISE" => "48D1CC", + "MEDIUMVIOLETRED" => "C71585", + "MIDNIGHTBLUE" => "191970", + "MINTCREAM" => "F5FFFA", + "MISTYROSE1" => "FFE4E1", + "MISTYROSE2" => "EED5D2", + "MISTYROSE3" => "CDB7B5", + "MISTYROSE4" => "8B7D7B", + "MOCCASIN" => "FFE4B5", + "NAVAJOWHITE1" => "FFDEAD", + "NAVAJOWHITE2" => "EECFA1", + "NAVAJOWHITE3" => "CDB38B", + "NAVAJOWHITE4" => "8B795E", + "NAVY" => "000080", + "NAVYBLUE" => "000080", + "OLDLACE" => "FDF5E6", + "OLIVE" => "808000", + "OLIVEDRAB" => "6B8E23", + "OLIVEDRAB1" => "C0FF3E", + "OLIVEDRAB2" => "B3EE3A", + "OLIVEDRAB4" => "698B22", + "ORANGE" => "FFA500", + "ORANGE1" => "FFA500", + "ORANGE2" => "EE9A00", + "ORANGE3" => "CD8500", + "ORANGE4" => "8B5A00", + "ORANGERED1" => "FF4500", + "ORANGERED2" => "EE4000", + "ORANGERED3" => "CD3700", + "ORANGERED4" => "8B2500", + "ORCHID" => "DA70D6", + "ORCHID1" => "FF83FA", + "ORCHID2" => "EE7AE9", + "ORCHID3" => "CD69C9", + "ORCHID4" => "8B4789", + "PALE" => "DB7093", + "PALEGOLDENROD" => "EEE8AA", + "PALEGREEN" => "98FB98", + "PALEGREEN1" => "9AFF9A", + "PALEGREEN2" => "90EE90", + "PALEGREEN3" => "7CCD7C", + "PALEGREEN4" => "548B54", + "PALETURQUOISE" => "AFEEEE", + "PALETURQUOISE1" => "BBFFFF", + "PALETURQUOISE2" => "AEEEEE", + "PALETURQUOISE3" => "96CDCD", + "PALETURQUOISE4" => "668B8B", + "PALEVIOLETRED" => "DB7093", + "PALEVIOLETRED1" => "FF82AB", + "PALEVIOLETRED2" => "EE799F", + "PALEVIOLETRED3" => "CD6889", + "PALEVIOLETRED4" => "8B475D", + "PAPAYAWHIP" => "FFEFD5", + "PEACHPUFF1" => "FFDAB9", + "PEACHPUFF2" => "EECBAD", + "PEACHPUFF3" => "CDAF95", + "PEACHPUFF4" => "8B7765", + "PINK" => "FFC0CB", + "PINK1" => "FFB5C5", + "PINK2" => "EEA9B8", + "PINK3" => "CD919E", + "PINK4" => "8B636C", + "PLUM" => "DDA0DD", + "PLUM1" => "FFBBFF", + "PLUM2" => "EEAEEE", + "PLUM3" => "CD96CD", + "PLUM4" => "8B668B", + "POWDERBLUE" => "B0E0E6", + "PURPLE" => "A020F0", + "REBECCAPURPLE" => "663399", + "PURPLE1" => "9B30FF", + "PURPLE2" => "912CEE", + "PURPLE3" => "7D26CD", + "PURPLE4" => "551A8B", + "RED" => "FF0000", + "RED1" => "FF0000", + "RED2" => "EE0000", + "RED3" => "CD0000", + "RED4" => "8B0000", + "ROSYBROWN" => "BC8F8F", + "ROSYBROWN1" => "FFC1C1", + "ROSYBROWN2" => "EEB4B4", + "ROSYBROWN3" => "CD9B9B", + "ROSYBROWN4" => "8B6969", + "ROYALBLUE" => "4169E1", + "ROYALBLUE1" => "4876FF", + "ROYALBLUE2" => "436EEE", + "ROYALBLUE3" => "3A5FCD", + "ROYALBLUE4" => "27408B", + "SADDLEBROWN" => "8B4513", + "SALMON" => "FA8072", + "SALMON1" => "FF8C69", + "SALMON2" => "EE8262", + "SALMON3" => "CD7054", + "SALMON4" => "8B4C39", + "SANDYBROWN" => "F4A460", + "SEAGREEN1" => "54FF9F", + "SEAGREEN2" => "4EEE94", + "SEAGREEN3" => "43CD80", + "SEAGREEN4" => "2E8B57", + "SEASHELL1" => "FFF5EE", + "SEASHELL2" => "EEE5DE", + "SEASHELL3" => "CDC5BF", + "SEASHELL4" => "8B8682", + "SIENNA" => "A0522D", + "SIENNA1" => "FF8247", + "SIENNA2" => "EE7942", + "SIENNA3" => "CD6839", + "SIENNA4" => "8B4726", + "SILVER" => "C0C0C0", + "SKYBLUE" => "87CEEB", + "SKYBLUE1" => "87CEFF", + "SKYBLUE2" => "7EC0EE", + "SKYBLUE3" => "6CA6CD", + "SKYBLUE4" => "4A708B", + "SLATEBLUE" => "6A5ACD", + "SLATEBLUE1" => "836FFF", + "SLATEBLUE2" => "7A67EE", + "SLATEBLUE3" => "6959CD", + "SLATEBLUE4" => "473C8B", + "SLATEGRAY" => "708090", + "SLATEGRAY1" => "C6E2FF", + "SLATEGRAY2" => "B9D3EE", + "SLATEGRAY3" => "9FB6CD", + "SLATEGRAY4" => "6C7B8B", + "SNOW1" => "FFFAFA", + "SNOW2" => "EEE9E9", + "SNOW3" => "CDC9C9", + "SNOW4" => "8B8989", + "SPRINGGREEN1" => "00FF7F", + "SPRINGGREEN2" => "00EE76", + "SPRINGGREEN3" => "00CD66", + "SPRINGGREEN4" => "008B45", + "STEELBLUE" => "4682B4", + "STEELBLUE1" => "63B8FF", + "STEELBLUE2" => "5CACEE", + "STEELBLUE3" => "4F94CD", + "STEELBLUE4" => "36648B", + "TAN" => "D2B48C", + "TAN1" => "FFA54F", + "TAN2" => "EE9A49", + "TAN3" => "CD853F", + "TAN4" => "8B5A2B", + "TEAL" => "008080", + "THISTLE" => "D8BFD8", + "THISTLE1" => "FFE1FF", + "THISTLE2" => "EED2EE", + "THISTLE3" => "CDB5CD", + "THISTLE4" => "8B7B8B", + "TOMATO1" => "FF6347", + "TOMATO2" => "EE5C42", + "TOMATO3" => "CD4F39", + "TOMATO4" => "8B3626", + "TURQUOISE" => "40E0D0", + "TURQUOISE1" => "00F5FF", + "TURQUOISE2" => "00E5EE", + "TURQUOISE3" => "00C5CD", + "TURQUOISE4" => "00868B", + "VIOLET" => "EE82EE", + "VIOLETRED" => "D02090", + "VIOLETRED1" => "FF3E96", + "VIOLETRED2" => "EE3A8C", + "VIOLETRED3" => "CD3278", + "VIOLETRED4" => "8B2252", + "WHEAT" => "F5DEB3", + "WHEAT1" => "FFE7BA", + "WHEAT2" => "EED8AE", + "WHEAT3" => "CDBA96", + "WHEAT4" => "8B7E66", + "WHITE" => "FFFFFF", + "WHITESMOKE" => "F5F5F5", + "YELLOW" => "FFFF00", + "YELLOW1" => "FFFF00", + "YELLOW2" => "EEEE00", + "YELLOW3" => "CDCD00", + "YELLOW4" => "8B8B00", + "YELLOWGREEN" => "9ACD32", +}; #[test] fn convert_test() { let html = r#"test
TEST
TEST
"#; - let result = html_to_richtext(html).unwrap(); + let _unused = html_to_richtext(html).unwrap(); } diff --git a/src/helper/number_format.rs b/src/helper/number_format.rs index 9c7f6d71..f57371e1 100644 --- a/src/helper/number_format.rs +++ b/src/helper/number_format.rs @@ -5,28 +5,31 @@ mod percentage_formater; use std::borrow::Cow; -use crate::helper::date::*; -use crate::structs::Color; -use crate::structs::NumberingFormat; -use fancy_regex::Captures; -use fancy_regex::Matches; -use fancy_regex::Regex; -use thousands::Separable; +use fancy_regex::{ + Matches, + Regex, +}; + +use crate::{ + helper::utils::compile_regex, + structs::NumberingFormat, +}; pub struct Split<'r, 't> { finder: Matches<'r, 't>, - last: usize, + last: usize, } #[inline] +#[must_use] pub fn split<'r, 't>(regex: &'r Regex, text: &'t str) -> Split<'r, 't> { Split { finder: regex.find_iter(text), - last: 0, + last: 0, } } -impl<'r, 't> Iterator for Split<'r, 't> { +impl<'t> Iterator for Split<'_, 't> { type Item = &'t str; fn next(&mut self) -> Option { @@ -50,13 +53,20 @@ impl<'r, 't> Iterator for Split<'r, 't> { } } -lazy_static! { - pub static ref ESCAPE_REGEX: Regex = - Regex::new(r#"(\\\(((.)(?!((AM\/PM)|(A\/P)))|([^ ])))(?=(?:[^"]|"[^"]*")*$)"#).unwrap(); - pub static ref SECTION_REGEX: Regex = Regex::new(r#"(;)(?=(?:[^"]|"[^"]*")*$)"#).unwrap(); - pub static ref DATE_TIME_REGEX: Regex = - Regex::new(r#"(\[\$[A-Z]*-[0-9A-F]*\])*[hmsdy](?=(?:[^"]|"[^"]*")*$)"#).unwrap(); - pub static ref PERCENT_DOLLAR_REGEX: Regex = Regex::new("%$").unwrap(); +pub fn get_escape_regex() -> &'static Regex { + compile_regex!(r#"(\\\(((.)(?!((AM\/PM)|(A\/P)))|([^ ])))(?=(?:[^"]|"[^"]*")*$)"#) +} + +pub fn get_section_regex() -> &'static Regex { + compile_regex!(r#"(;)(?=(?:[^"]|"[^"]*")*$)"#) +} + +pub fn get_date_time_regex() -> &'static Regex { + compile_regex!(r#"(\[\$[A-Z]*-[0-9A-F]*\])*[hmsdy](?=(?:[^"]|"[^"]*")*$)"#) +} + +pub fn get_percent_dollar_regex() -> &'static Regex { + compile_regex!("%$") } pub fn to_formatted_string, P: AsRef>(value: S, format: P) -> String { @@ -80,134 +90,125 @@ pub fn to_formatted_string, P: AsRef>(value: S, format: P) -> // Convert any other escaped characters to quoted strings, e.g. (\T to "T") - let mut format = ESCAPE_REGEX.replace_all(&format, r#""$0""#); + let mut format = get_escape_regex().replace_all(&format, r#""$0""#); - // Get the sections, there can be up to four sections, separated with a semi-colon (but only if not a quoted literal) + // Get the sections, there can be up to four sections, separated with a + // semi-colon (but only if not a quoted literal) - let sections: Vec<&str> = split(&SECTION_REGEX, &format).collect(); + let sections: Vec<&str> = split(get_section_regex(), &format).collect(); - let (_, split_format, split_value) = split_format(sections, &value.parse::().unwrap()); + let (_, split_format, split_value) = split_format(sections, value.parse::().unwrap()); format = Cow::Owned(split_format); value = Cow::Owned(split_value); // In Excel formats, "_" is used to add spacing, - // The following character indicates the size of the spacing, which we can't do in HTML, so we just use a standard space - let re = Regex::new("_.").unwrap(); + // The following character indicates the size of the spacing, which we can't + // do in HTML, so we just use a standard space + let re = compile_regex!("_."); let format = re.replace_all(&format, " "); - // Let's begin inspecting the format and converting the value to a formatted string + // Let's begin inspecting the format and converting the value to a formatted + // string // Check for date/time characters (not inside quotes) - if DATE_TIME_REGEX.is_match(&format).unwrap_or(false) { + if get_date_time_regex().is_match(&format).unwrap_or(false) { // datetime format - value = date_formater::format_as_date(&value.parse::().unwrap(), &format); + value = date_formater::format_as_date(value.parse::().unwrap(), &format); } else if format.starts_with('"') && format.ends_with('"') { let conv_format = format.trim_matches('"').parse::().unwrap(); value = Cow::Owned(conv_format.to_string()); - } else if PERCENT_DOLLAR_REGEX.is_match(&format).unwrap_or(false) { + } else if get_percent_dollar_regex() + .is_match(&format) + .unwrap_or(false) + { // % number format - value = percentage_formater::format_as_percentage(&value.parse::().unwrap(), &format); + value = percentage_formater::format_as_percentage(value.parse::().unwrap(), &format); } else { - value = number_formater::format_as_number(&value.parse::().unwrap(), &format); + value = number_formater::format_as_number(value.parse::().unwrap(), &format); } value.trim().to_string() } -fn split_format(sections: Vec<&str>, value: &f64) -> (String, String, String) { +fn split_format(sections: Vec<&str>, value: f64) -> (String, String, String) { let mut converted_sections: Vec = Vec::new(); - // Extract the relevant section depending on whether number is positive, negative, or zero? - // Text not supported yet. + // Extract the relevant section depending on whether number is positive, + // negative, or zero? Text not supported yet. // Here is how the sections apply to various values in Excel: // 1 section: [POSITIVE/NEGATIVE/ZERO/TEXT] // 2 sections: [POSITIVE/ZERO/TEXT] [NEGATIVE] // 3 sections: [POSITIVE/TEXT] [NEGATIVE] [ZERO] // 4 sections: [POSITIVE] [NEGATIVE] [ZERO] [TEXT] let cnt: usize = sections.len(); - let color_regex: String = format!("{}{}{}", "\\[(", Color::NAMED_COLORS.join("|"), ")\\]"); - let cond_regex = r"\[(>|>=|<|<=|=|<>)([+-]?\d+([.]\d+)?)\]"; - let color_re = Regex::new(&color_regex).unwrap(); - let cond_re = Regex::new(cond_regex).unwrap(); - - let mut colors = [ - String::from(""), - String::from(""), - String::from(""), - String::from(""), - String::from(""), - ]; - let mut condops = [ - String::from(""), - String::from(""), - String::from(""), - String::from(""), - String::from(""), - ]; - let mut condvals = [ - String::from("0"), - String::from("0"), - String::from("0"), - String::from("0"), - String::from("0"), - ]; + + // format!("{}{}{}", "\\[(", Color::NAMED_COLORS.join("|"), ")\\]"); + let color_re = compile_regex!(r"\[(Black|White|Red|Green|Blue|Yellow|Magenta|Cyan)\]"); + let cond_re = compile_regex!(r"\[(>|>=|<|<=|=|<>)([+-]?\d+([.]\d+)?)\]"); + + let mut colors = [""; 5]; + let mut condops = [""; 5]; + + let mut condvals = ["0"; 5]; sections.into_iter().enumerate().for_each(|(idx, section)| { let mut converted_section = section.to_string(); - if color_re.find(section).ok().flatten().is_some() { - let mut item: Vec = Vec::new(); - for ite in color_re.captures(section).ok().flatten().unwrap().iter() { - item.push(ite.unwrap().as_str().to_string()); + + // Process color matching + if let Some(captures) = color_re.captures(section).ok().flatten() { + let items: Vec<&str> = captures + .iter() + .filter_map(|cap| cap.map(|c| c.as_str())) + .collect(); + + if let Some(first_item) = items.first() { + colors[idx].clone_from(first_item); } - std::mem::replace(&mut colors[idx], item.get(0).unwrap().to_string()); + converted_section = color_re.replace_all(section, "").to_string(); } - if cond_re.find(section).ok().flatten().is_some() { - let mut item: Vec = Vec::new(); - for ite in cond_re.captures(section).ok().flatten().unwrap().iter() { - match ite { - Some(v) => item.push(v.as_str().to_string()), - None => {} - } - } - match item.get(1) { - Some(v) => { - std::mem::replace(&mut condops[idx], v.to_string()); - } - None => {} + + // Process conditional matching + if let Some(captures) = cond_re.captures(section).ok().flatten() { + let items: Vec<&str> = captures + .iter() + .filter_map(|cap| cap.map(|c| c.as_str())) + .collect(); + + if let Some(v) = items.get(1) { + condops[idx].clone_from(v); } - match item.get(2) { - Some(v) => { - std::mem::replace(&mut condvals[idx], v.to_string()); - } - None => {} + if let Some(v) = items.get(2) { + condvals[idx].clone_from(v); } + converted_section = cond_re.replace_all(section, "").to_string(); } + converted_sections.insert(idx, converted_section); }); - let mut color = &colors[0]; - let mut format: &str = &converted_sections[0]; - let mut absval = *value; + let mut color = colors[0]; + let mut format = &converted_sections[0]; + let mut absval = value; match cnt { 2 => { absval = absval.abs(); - let condval_one = &condvals[0].parse::().unwrap(); - if !split_format_compare(value, &condops[0], condval_one, ">=", &0f64) { - color = &colors[1]; + let condval_one = condvals[0].parse::().unwrap(); + if !split_format_compare(value, condops[0], condval_one, ">=", 0f64) { + color = colors[1]; format = &converted_sections[1]; } } 3 | 4 => { absval = absval.abs(); - let condval_one = &condvals[0].parse::().unwrap(); - let condval_two = &condvals[1].parse::().unwrap(); - if !split_format_compare(value, &condops[0], condval_one, ">", &0f64) { - if split_format_compare(value, &condops[1], condval_two, "<", &0f64) { - color = &colors[1]; + let condval_one = condvals[0].parse::().unwrap(); + let condval_two = condvals[1].parse::().unwrap(); + if !split_format_compare(value, condops[0], condval_one, ">", 0f64) { + if split_format_compare(value, condops[1], condval_two, "<", 0f64) { + color = colors[1]; format = &converted_sections[1]; } else { - color = &colors[2]; + color = colors[2]; format = &converted_sections[2]; } } @@ -217,7 +218,8 @@ fn split_format(sections: Vec<&str>, value: &f64) -> (String, String, String) { (color.to_string(), format.into(), absval.to_string()) } -fn split_format_compare(value: &f64, cond: &str, val: &f64, dfcond: &str, dfval: &f64) -> bool { +#[allow(clippy::float_cmp)] +fn split_format_compare(value: f64, cond: &str, val: f64, dfcond: &str, dfval: f64) -> bool { let mut check_cond = cond; let mut check_val = val; if cond.is_empty() { @@ -239,182 +241,182 @@ fn split_format_compare(value: &f64, cond: &str, val: &f64, dfcond: &str, dfval: fn test_to_formatted_string_date() { let value = String::from("45435"); // 2024/5/23 assert_eq!( - r#"2024-05-23"#, + r"2024-05-23", to_formatted_string(&value, NumberingFormat::FORMAT_DATE_YYYYMMDD2) ); assert_eq!( - r#"2024-05-23"#, + r"2024-05-23", to_formatted_string(&value, NumberingFormat::FORMAT_DATE_YYYYMMDD) ); assert_eq!( - r#"23-05-2024"#, + r"23-05-2024", to_formatted_string(&value, NumberingFormat::FORMAT_DATE_DDMMYYYY) ); assert_eq!( - r#"23/05/2024"#, + r"23/05/2024", to_formatted_string(&value, NumberingFormat::FORMAT_DATE_DDMMYYYYSLASH) ); assert_eq!( - r#"23/5/24"#, + r"23/5/24", to_formatted_string(&value, NumberingFormat::FORMAT_DATE_DMYSLASH) ); assert_eq!( - r#"23-5-24"#, + r"23-5-24", to_formatted_string(&value, NumberingFormat::FORMAT_DATE_DMYMINUS) ); assert_eq!( - r#"23-5"#, + r"23-5", to_formatted_string(&value, NumberingFormat::FORMAT_DATE_DMMINUS) ); assert_eq!( - r#"5-24"#, + r"5-24", to_formatted_string(&value, NumberingFormat::FORMAT_DATE_MYMINUS) ); assert_eq!( - r#"05-23-24"#, + r"05-23-24", to_formatted_string(&value, NumberingFormat::FORMAT_DATE_XLSX14) ); assert_eq!( - r#"23-May-24"#, + r"23-May-24", to_formatted_string(&value, NumberingFormat::FORMAT_DATE_XLSX15) ); assert_eq!( - r#"23-May"#, + r"23-May", to_formatted_string(&value, NumberingFormat::FORMAT_DATE_XLSX16) ); assert_eq!( - r#"May-24"#, + r"May-24", to_formatted_string(&value, NumberingFormat::FORMAT_DATE_XLSX17) ); assert_eq!( - r#"5/23/24 0:00"#, + r"5/23/24 0:00", to_formatted_string(&value, NumberingFormat::FORMAT_DATE_XLSX22) ); assert_eq!( - r#"23/5/24 0:00"#, + r"23/5/24 0:00", to_formatted_string(&value, NumberingFormat::FORMAT_DATE_DATETIME) ); assert_eq!( - r#"12:00 am"#, + r"12:00 am", to_formatted_string(&value, NumberingFormat::FORMAT_DATE_TIME1) ); assert_eq!( - r#"12:00:00 am"#, + r"12:00:00 am", to_formatted_string(&value, NumberingFormat::FORMAT_DATE_TIME2) ); assert_eq!( - r#"0:00"#, + r"0:00", to_formatted_string(&value, NumberingFormat::FORMAT_DATE_TIME3) ); assert_eq!( - r#"0:00:00"#, + r"0:00:00", to_formatted_string(&value, NumberingFormat::FORMAT_DATE_TIME4) ); assert_eq!( - r#"00:00"#, + r"00:00", to_formatted_string(&value, NumberingFormat::FORMAT_DATE_TIME5) ); assert_eq!( - r#"0:00:00"#, + r"0:00:00", to_formatted_string(&value, NumberingFormat::FORMAT_DATE_TIME6) ); assert_eq!( - r#"0:00:00"#, + r"0:00:00", to_formatted_string(&value, NumberingFormat::FORMAT_DATE_TIME8) ); assert_eq!( - r#"2024/05/23"#, + r"2024/05/23", to_formatted_string(&value, NumberingFormat::FORMAT_DATE_YYYYMMDDSLASH) ); let value = String::from("44349.211134259262"); // 2021/06/02 05:04:02 assert_eq!( - r#"2021-06-02"#, + r"2021-06-02", to_formatted_string(&value, NumberingFormat::FORMAT_DATE_YYYYMMDD2) ); assert_eq!( - r#"2021-06-02"#, + r"2021-06-02", to_formatted_string(&value, NumberingFormat::FORMAT_DATE_YYYYMMDD) ); assert_eq!( - r#"02-06-2021"#, + r"02-06-2021", to_formatted_string(&value, NumberingFormat::FORMAT_DATE_DDMMYYYY) ); assert_eq!( - r#"02/06/2021"#, + r"02/06/2021", to_formatted_string(&value, NumberingFormat::FORMAT_DATE_DDMMYYYYSLASH) ); assert_eq!( - r#"2/6/21"#, + r"2/6/21", to_formatted_string(&value, NumberingFormat::FORMAT_DATE_DMYSLASH) ); assert_eq!( - r#"2-6-21"#, + r"2-6-21", to_formatted_string(&value, NumberingFormat::FORMAT_DATE_DMYMINUS) ); assert_eq!( - r#"2-6"#, + r"2-6", to_formatted_string(&value, NumberingFormat::FORMAT_DATE_DMMINUS) ); assert_eq!( - r#"6-21"#, + r"6-21", to_formatted_string(&value, NumberingFormat::FORMAT_DATE_MYMINUS) ); assert_eq!( - r#"06-02-21"#, + r"06-02-21", to_formatted_string(&value, NumberingFormat::FORMAT_DATE_XLSX14) ); assert_eq!( - r#"2-Jun-21"#, + r"2-Jun-21", to_formatted_string(&value, NumberingFormat::FORMAT_DATE_XLSX15) ); assert_eq!( - r#"2-Jun"#, + r"2-Jun", to_formatted_string(&value, NumberingFormat::FORMAT_DATE_XLSX16) ); assert_eq!( - r#"Jun-21"#, + r"Jun-21", to_formatted_string(&value, NumberingFormat::FORMAT_DATE_XLSX17) ); assert_eq!( - r#"6/2/21 5:04"#, + r"6/2/21 5:04", to_formatted_string(&value, NumberingFormat::FORMAT_DATE_XLSX22) ); assert_eq!( - r#"2/6/21 5:04"#, + r"2/6/21 5:04", to_formatted_string(&value, NumberingFormat::FORMAT_DATE_DATETIME) ); assert_eq!( - r#"5:04 am"#, + r"5:04 am", to_formatted_string(&value, NumberingFormat::FORMAT_DATE_TIME1) ); assert_eq!( - r#"5:04:02 am"#, + r"5:04:02 am", to_formatted_string(&value, NumberingFormat::FORMAT_DATE_TIME2) ); assert_eq!( - r#"5:04"#, + r"5:04", to_formatted_string(&value, NumberingFormat::FORMAT_DATE_TIME3) ); assert_eq!( - r#"5:04:02"#, + r"5:04:02", to_formatted_string(&value, NumberingFormat::FORMAT_DATE_TIME4) ); assert_eq!( - r#"04:02"#, + r"04:02", to_formatted_string(&value, NumberingFormat::FORMAT_DATE_TIME5) ); assert_eq!( - r#"5:04:02"#, + r"5:04:02", to_formatted_string(&value, NumberingFormat::FORMAT_DATE_TIME6) ); assert_eq!( - r#"5:04:02"#, + r"5:04:02", to_formatted_string(&value, NumberingFormat::FORMAT_DATE_TIME8) ); assert_eq!( - r#"2021/06/02"#, + r"2021/06/02", to_formatted_string(&value, NumberingFormat::FORMAT_DATE_YYYYMMDDSLASH) ); - assert_eq!(r#"2"#, to_formatted_string(&value, "d")) + assert_eq!(r"2", to_formatted_string(&value, "d")); } diff --git a/src/helper/number_format/date_formater.rs b/src/helper/number_format/date_formater.rs index 7e836afd..62c80445 100644 --- a/src/helper/number_format/date_formater.rs +++ b/src/helper/number_format/date_formater.rs @@ -1,12 +1,11 @@ use std::borrow::Cow; -use crate::helper::date::*; -use crate::structs::Color; -use crate::structs::NumberingFormat; use fancy_regex::Captures; -use fancy_regex::Matches; -use fancy_regex::Regex; -use thousands::Separable; + +use crate::helper::{ + date::excel_to_date_time_object, + utils::compile_regex, +}; const DATE_FORMAT_REPLACEMENTS: &[(&str, &str)] = &[ // first remove escapes related to non-format characters @@ -57,19 +56,19 @@ const DATE_FORMAT_REPLACEMENTS_24: &[(&str, &str)] = &[("hh", "%H"), ("h", "%-H" const DATE_FORMAT_REPLACEMENTS_12: &[(&str, &str)] = &[("hh", "%I"), ("h", "%-I")]; -pub(crate) fn format_as_date<'input>(value: &f64, format: &'input str) -> Cow<'input, str> { +pub(crate) fn format_as_date(value: f64, format: &str) -> Cow<'_, str> { let format = Cow::Borrowed(format); // strip off first part containing e.g. [$-F800] or [$USD-409] // general syntax: [$-] // language info is in hexadecimal // strip off chinese part like [DBNum1][$-804] - let re = Regex::new(r"^(\[[0-9A-Za-z]*\])*(\[\$[A-Z]*-[0-9A-F]*\])").unwrap(); - let format = re.replace_all(&format, r#""#); + let re = compile_regex!(r"^(\[[0-9A-Za-z]*\])*(\[\$[A-Z]*-[0-9A-F]*\])"); + let format = re.replace_all(&format, r""); - // OpenOffice.org uses upper-case number formats, e.g. 'YYYY', convert to lower-case; - // but we don't want to change any quoted strings - let re = Regex::new(r#"(?:^|")([^"]*)(?:$|")"#).unwrap(); + // OpenOffice.org uses upper-case number formats, e.g. 'YYYY', convert to + // lower-case; but we don't want to change any quoted strings + let re = compile_regex!(r#"(?:^|")([^"]*)(?:$|")"#); let mut format = re.replace_all(&format, |caps: &Captures| { let caps_string = caps.get(0).unwrap().as_str(); caps_string.to_lowercase() @@ -85,31 +84,32 @@ pub(crate) fn format_as_date<'input>(value: &f64, format: &'input str) -> Cow<'i for (before, after) in DATE_FORMAT_REPLACEMENTS { block = block.replace(before, after); } - if !block.contains("%P") { + if block.contains("%P") { + for (before, after) in DATE_FORMAT_REPLACEMENTS_12 { + block = block.replace(before, after); + } + } else { // 24-hour time format // when [h]:mm format, the [h] should replace to the hours of the value * 24 if block.contains("[h]") { let hours = value * 24f64; - block = block.replace("[h]", hours.to_string().as_str()); + block = block.replace("[h]", &hours.to_string()); converted_blocks.push(block); continue; } for (before, after) in DATE_FORMAT_REPLACEMENTS_24 { block = block.replace(before, after); } - } else { - for (before, after) in DATE_FORMAT_REPLACEMENTS_12 { - block = block.replace(before, after); - } } } converted_blocks.push(block); i += 1; } - format = Cow::Owned(converted_blocks.join(r#""#)); + format = Cow::Owned(converted_blocks.join(r"")); - // escape any quoted characters so that DateTime format() will render them correctly - let re = Regex::new(r#""(.*)""#).unwrap(); + // escape any quoted characters so that DateTime format() will render them + // correctly + let re = compile_regex!(r#""(.*)""#); let format = re.replace_all(&format, |caps: &Captures| { let caps_string = caps.get(0).unwrap().as_str(); caps_string.to_lowercase() diff --git a/src/helper/number_format/fraction_formater.rs b/src/helper/number_format/fraction_formater.rs index 463d1f0c..0b4cf4df 100644 --- a/src/helper/number_format/fraction_formater.rs +++ b/src/helper/number_format/fraction_formater.rs @@ -1,15 +1,5 @@ -use std::borrow::Cow; - -use crate::helper::date::*; -use crate::structs::Color; -use crate::structs::NumberingFormat; -use fancy_regex::Captures; -use fancy_regex::Matches; -use fancy_regex::Regex; -use thousands::Separable; - -pub(crate) fn format_as_fraction(value: &f64, format: &str) -> String { - let sign = if value < &0f64 { "-" } else { "" }; +pub(crate) fn format_as_fraction(value: f64, format: &str) -> String { + let sign = if value < 0f64 { "-" } else { "" }; let integer_part = value.abs().floor(); let decimal_part = (value.abs() % 1f64) @@ -18,9 +8,9 @@ pub(crate) fn format_as_fraction(value: &f64, format: &str) -> String { .parse::() .unwrap(); let decimal_length = decimal_part.to_string().len(); - let decimal_divisor = 10f64.powi(decimal_length as i32); + let decimal_divisor = 10f64.powi(num_traits::cast(decimal_length).unwrap()); - let gcd = gcd(&decimal_part, &decimal_divisor); + let gcd = gcd(decimal_part, decimal_divisor); let mut adjusted_decimal_part = decimal_part / gcd; let adjusted_decimal_divisor = decimal_divisor / gcd; @@ -52,7 +42,7 @@ pub(crate) fn format_as_fraction(value: &f64, format: &str) -> String { if check_format == "? ?" { let mut integer_part_str = integer_part.to_string(); if integer_part == 0f64 { - integer_part_str = String::from(""); + integer_part_str = String::new(); } result = format!( "{}{} {}/{}", @@ -70,10 +60,6 @@ pub(crate) fn format_as_fraction(value: &f64, format: &str) -> String { } #[inline] -fn gcd(a: &f64, b: &f64) -> f64 { - if b == &0f64 { - *a - } else { - gcd(b, &(a % b)) - } +fn gcd(a: f64, b: f64) -> f64 { + if b == 0f64 { a } else { gcd(b, a % b) } } diff --git a/src/helper/number_format/number_formater.rs b/src/helper/number_format/number_formater.rs index eebc31ed..bd73d7a9 100644 --- a/src/helper/number_format/number_formater.rs +++ b/src/helper/number_format/number_formater.rs @@ -1,101 +1,78 @@ -use super::fraction_formater::*; -use fancy_regex::Regex; use std::borrow::Cow; + use thousands::Separable; -pub(crate) fn format_as_number<'input>(value: &f64, format: &'input str) -> Cow<'input, str> { - lazy_static! { - static ref THOUSANDS_SEP_REGEX: Regex = Regex::new(r#"(#,#|0,0)"#).unwrap(); - static ref SCALE_REGEX: Regex = Regex::new(r#"(#|0)(,+)"#).unwrap(); - static ref TRAILING_COMMA_REGEX: Regex = Regex::new("(#|0),+").unwrap(); - static ref FRACTION_REGEX: Regex = Regex::new(r"#?.*\?{1,2}\/\?{1,2}").unwrap(); - static ref SQUARE_BRACKET_REGEX: Regex = Regex::new(r"\[[^\]]+\]").unwrap(); - static ref NUMBER_REGEX: Regex = Regex::new(r"(0+)(\.?)(0*)").unwrap(); - } +use super::fraction_formater::format_as_fraction; +use crate::helper::utils::compile_regex; - let mut value = value.to_string(); +pub(crate) fn format_as_number(value: f64, format: &str) -> Cow<'_, str> { + let thousands_sep_regex = compile_regex!(r"(#,#|0,0)"); + let scale_regex = compile_regex!(r"(#|0)(,+)"); + let trailing_comma_regex = compile_regex!("(#|0),+"); + let fraction_regex = compile_regex!(r"#?.*\?{1,2}\/\?{1,2}"); + let square_bracket_regex = compile_regex!(r"\[[^\]]+\]"); + let number_regex = compile_regex!(r"(0+)(\.?)(0*)"); - // The "_" in this string has already been stripped out, - // so this test is never true. Furthermore, testing - // on Excel shows this format uses Euro symbol, not "EUR". - //if ($format === self::FORMAT_CURRENCY_EUR_SIMPLE) { - // return 'EUR ' . sprintf('%1.2f', $value); - //} + let mut value = value.to_string(); - // Some non-number strings are quoted, so we'll get rid of the quotes, likewise any positional * symbols let mut format = format.replace(['"', '*'], ""); - // Find out if we need thousands separator - // This is indicated by a comma enclosed by a digit placeholder: - // #,# or 0,0 - - let use_thousands = THOUSANDS_SEP_REGEX.is_match(&format).unwrap_or(false); + let use_thousands = thousands_sep_regex.is_match(&format).unwrap_or(false); if use_thousands { format = format.replace("0,0", "00"); format = format.replace("#,#", "##"); } - // Scale thousands, millions,... - // This is indicated by a number of commas after a digit placeholder: - // #, or 0.0,, - let mut scale: f64 = 1f64; // same as no scale + let mut scale: f64 = 1f64; - if SCALE_REGEX.is_match(&format).unwrap_or(false) { - let mut matches: Vec = Vec::new(); - for ite in SCALE_REGEX.captures(&format).ok().flatten().unwrap().iter() { - matches.push(ite.unwrap().as_str().to_string()); + if scale_regex.is_match(&format).unwrap_or(false) { + let mut matches: Vec<&str> = Vec::new(); + for ite in scale_regex.captures(&format).ok().flatten().unwrap().iter() { + matches.push(ite.unwrap().as_str()); } - scale = 1000i32.pow(matches[2].len() as u32) as f64; + scale = f64::from(1000i32.pow(num_traits::cast(matches[2].len()).unwrap())); - // strip the commas - format = TRAILING_COMMA_REGEX.replace_all(&format, "$1").into() + format = trailing_comma_regex.replace_all(&format, "$1").into(); } - if FRACTION_REGEX.is_match(&format).unwrap_or(false) { + if fraction_regex.is_match(&format).unwrap_or(false) { if value.parse::().is_err() { - //println!("format as fraction {} {}", value, format); - value = format_as_fraction(&value.parse::().unwrap(), &format); + value = format_as_fraction(value.parse::().unwrap(), &format); } } else { - // Handle the number itself - - // scale number value = (value.parse::().unwrap() / scale).to_string(); - // Strip # format = format.replace('#', "0"); - // Remove \ format = format.replace('\\', ""); - // Remove locale code [$-###] format = format.replace("[$-.*]", ""); - // Trim format = format.trim().to_string(); - let m = SQUARE_BRACKET_REGEX.replace_all(&format, ""); + let m = square_bracket_regex.replace_all(&format, ""); - if NUMBER_REGEX.is_match(&m).unwrap_or(false) { + if number_regex.is_match(&m).unwrap_or(false) { let mut item: Vec = Vec::new(); - for ite in NUMBER_REGEX.captures(&m).ok().flatten().unwrap().iter() { + for ite in number_regex.captures(&m).ok().flatten().unwrap().iter() { item.push(ite.unwrap().as_str().to_string()); } value = format_straight_numeric_value( &value, &format, &item, - &use_thousands, + use_thousands, r"(0+)(\.?)(0*)", ); } } - let re = Regex::new(r"\$[^0-9]*").unwrap(); + let re = compile_regex!(r"\$[^0-9]*"); if re.find(&format).ok().flatten().is_some() { - let mut item: Vec = Vec::new(); - for ite in re.captures(&format).ok().flatten().unwrap().iter() { - item.push(ite.unwrap().as_str().to_string()); - } - value = format!("{}{}", item.get(0).unwrap(), value); - // // Currency or Accounting - // let currency_code = item.get(1).unwrap().to_string(); - // value = Regex::new(r#"\[\$([^\]]*)\]"#).unwrap().replace_all(&value, currency_code.as_str()).to_string(); + let item: Vec<&str> = re + .captures(&format) + .ok() + .flatten() + .unwrap() + .iter() + .map(|ite| ite.unwrap().as_str()) + .collect(); + value = format!("{}{}", item.first().unwrap(), value); } Cow::Owned(value) @@ -105,7 +82,7 @@ fn format_straight_numeric_value( value: &str, _format: &str, matches: &[String], - use_thousands: &bool, + use_thousands: bool, _number_regex: &str, ) -> String { let mut value = value.to_string(); @@ -113,13 +90,13 @@ fn format_straight_numeric_value( let right = matches.get(3).unwrap(); // minimun width of formatted number (including dot) - if *use_thousands { + if use_thousands { value = value.parse::().unwrap().separate_with_commas(); } let blocks: Vec<&str> = value.split('.').collect(); - let left_value = blocks.first().unwrap().to_string(); + let left_value = (*blocks.first().unwrap()).to_string(); let mut right_value = match blocks.get(1) { - Some(v) => v.to_string(), + Some(v) => (*v).to_string(), None => String::from("0"), }; if right.is_empty() { @@ -127,9 +104,9 @@ fn format_straight_numeric_value( } if right.len() != right_value.len() { if right_value == "0" { - right_value = right.to_string(); + right_value.clone_from(right); } else if right.len() > right_value.len() { - let pow = 10i32.pow(right.len() as u32); + let pow = 10i32.pow(num_traits::cast(right.len()).unwrap()); right_value = format!("{}", right_value.parse::().unwrap() * pow); } else { let mut right_value_conv: String = right_value.chars().take(right.len()).collect(); @@ -141,40 +118,18 @@ fn format_straight_numeric_value( right_value = right_value_conv; } } - value = format!("{}.{}", left_value, right_value); + value = format!("{left_value}.{right_value}"); value - - // if use_thousands == &true { - // value = value.parse::().unwrap().separate_with_commas(); - // dbg!(&value); - // value = Regex::new(&number_regex).unwrap().replace_all(&format, value.as_str()); - // dbg!(&value); - // } else { - // if Regex::new(r#"[0#]E[+-]0"#).unwrap().find(&format).is_some() { - // // Scientific format - // value = value.parse::().unwrap().to_string(); - // } else if Regex::new(r#"0([^\d\.]+)0"#).unwrap().find(&format).is_some() || format.find(".").is_some() { - // if value.parse::().unwrap() as usize as f64 == value.parse::().unwrap() && format.find(".").is_some() { - // let format_collect:Vec<&str> = format.split('.').collect(); - // let pow = 10i32.pow(format_collect.get(1).unwrap().len() as u32); - // value = format!("{}", value.parse::().unwrap() * pow); - // } - // value = complex_number_format_mask(&value.parse::().unwrap(), &format, &true); - // } else { - // value = format!("{:0width$.len$}", value, width = min_width, len = right.len()); - // value = Regex::new(&number_regex).unwrap().replace_all(&format, value.as_str()); - // } - // } - // value } +#[allow(dead_code)] fn merge_complex_number_format_masks(numbers: &[String], masks: &[String]) -> Vec { let mut decimal_count = numbers[1].len(); - let mut post_decimal_masks: Vec = Vec::new(); + let mut post_decimal_masks: Vec<&str> = Vec::new(); for mask in masks.iter().rev() { - post_decimal_masks.push(mask.to_string()); - decimal_count -= mask.to_string().len(); + post_decimal_masks.push(mask); + decimal_count -= mask.clone().len(); if decimal_count == 0 { break; } @@ -184,29 +139,27 @@ fn merge_complex_number_format_masks(numbers: &[String], masks: &[String]) -> Ve vec![masks.join("."), post_decimal_masks.join(".")] } -fn process_complex_number_format_mask(number: &f64, mask: &str) -> String { +#[allow(dead_code)] +fn process_complex_number_format_mask(number: f64, mask: &str) -> String { let mut result = number.to_string(); - let mut mask = mask.to_string(); - let re = Regex::new(r#"0+"#).unwrap(); - let mut masking_blocks: Vec<(String, usize)> = Vec::new(); - let mut masking_str: Vec = Vec::new(); + let re = compile_regex!(r"0+"); + let mut masking_blocks: Vec<(&str, usize)> = Vec::new(); + let mut masking_str: Vec<&str> = Vec::new(); let mut masking_beg: Vec = Vec::new(); - for ite in re.captures(&mask).ok().flatten().unwrap().iter() { - masking_str.push(ite.unwrap().as_str().to_string()); + for ite in re.captures(mask).ok().flatten().unwrap().iter() { + masking_str.push(ite.unwrap().as_str()); } - for pos in re.captures(&mask).ok().flatten().unwrap().iter() { + for pos in re.captures(mask).ok().flatten().unwrap().iter() { let beg = pos.unwrap().start(); masking_beg.push(beg); } for i in 0..masking_str.len() { - masking_blocks.push(( - masking_str.get(i).unwrap().clone(), - *masking_beg.get(i).unwrap(), - )); + masking_blocks.push((masking_str.get(i).unwrap(), *masking_beg.get(i).unwrap())); } + let mut mask = mask.to_string(); if masking_blocks.len() > 1 { - let mut number = *number; + let mut number = number; let mut offset: usize = 0; for (block, pos) in masking_blocks.iter().rev() { let divisor = format!("{}{}", 1, block).parse::().unwrap(); @@ -228,11 +181,12 @@ fn process_complex_number_format_mask(number: &f64, mask: &str) -> String { result } -fn complex_number_format_mask(number: &f64, mask: &str, split_on_point: &bool) -> String { - let sign = number < &0.0; +#[allow(dead_code)] +fn complex_number_format_mask(number: f64, mask: &str, split_on_point: bool) -> String { + let sign = number < 0.0; let number = number.abs(); - if *split_on_point && mask.contains('.') && number.to_string().contains('.') { + if split_on_point && mask.contains('.') && number.to_string().contains('.') { let number_str = number.to_string(); let numbers_as: Vec<&str> = number_str.split('.').collect(); let mut numbers: Vec = Vec::new(); @@ -248,16 +202,16 @@ fn complex_number_format_mask(number: &f64, mask: &str, split_on_point: &bool) - masks = merge_complex_number_format_masks(&numbers, &masks); } let result1 = - complex_number_format_mask(&numbers[0].parse::().unwrap(), &masks[0], &false); + complex_number_format_mask(numbers[0].parse::().unwrap(), &masks[0], false); let result2 = complex_number_format_mask( - &numbers[1] + numbers[1] .chars() .rev() .collect::() .parse::() .unwrap(), &masks[1].chars().rev().collect::(), - &false, + false, ) .chars() .rev() @@ -266,6 +220,6 @@ fn complex_number_format_mask(number: &f64, mask: &str, split_on_point: &bool) - return format!("{}{}.{}", if sign { "-" } else { "" }, result1, result2); } - let result = process_complex_number_format_mask(&number, mask); + let result = process_complex_number_format_mask(number, mask); format!("{}{}", if sign { "-" } else { "" }, result) } diff --git a/src/helper/number_format/percentage_formater.rs b/src/helper/number_format/percentage_formater.rs index 333e788c..f515d81f 100644 --- a/src/helper/number_format/percentage_formater.rs +++ b/src/helper/number_format/percentage_formater.rs @@ -1,6 +1,6 @@ use std::borrow::Cow; -pub(crate) fn format_as_percentage<'input>(value: &f64, format: &'input str) -> Cow<'input, str> { +pub(crate) fn format_as_percentage(value: f64, format: &str) -> Cow<'_, str> { let mut value = value.to_string(); let mut format = Cow::Borrowed(format); format = Cow::Owned(format.replace('%', "")); diff --git a/src/helper/range.rs b/src/helper/range.rs index 962a16d1..6884971d 100644 --- a/src/helper/range.rs +++ b/src/helper/range.rs @@ -1,12 +1,11 @@ -use crate::helper::coordinate::*; - -use super::coordinate; +use crate::helper::coordinate::index_from_coordinate; /// `(col, row)` pub type BasicCellIndex = (u32, u32); /// # Returns /// `Vec<(col, row)>` +#[must_use] pub fn get_coordinate_list(range_str: &str) -> Vec { let (row_start, row_end, col_start, col_end) = get_start_and_end_point(range_str); @@ -15,8 +14,10 @@ pub fn get_coordinate_list(range_str: &str) -> Vec { .collect() } +#[must_use] pub fn get_start_and_end_point(range_str: &str) -> (u32, u32, u32, u32) { - let coordinate_collection: Vec<&str> = range_str.split(':').collect(); + let upper_rng_str = range_str.to_uppercase(); + let coordinate_collection: Vec<&str> = upper_rng_str.split(':').collect(); assert!( matches!(coordinate_collection.len(), 1 | 2), @@ -51,7 +52,7 @@ pub fn get_start_and_end_point(range_str: &str) -> (u32, u32, u32, u32) { None => { assert!(is_col_select, "Non-standard range."); } - }; + } match row { Some(v) => { @@ -67,11 +68,13 @@ pub fn get_start_and_end_point(range_str: &str) -> (u32, u32, u32, u32) { } #[inline] +#[must_use] pub fn get_split_range(range: &str) -> Vec<&str> { range.split(':').collect() } #[inline] +#[must_use] pub fn get_join_range(coordinate_list: &[String]) -> String { coordinate_list.join(":") } diff --git a/src/helper/string_helper.rs b/src/helper/string_helper.rs index f35830f8..a7fc1435 100644 --- a/src/helper/string_helper.rs +++ b/src/helper/string_helper.rs @@ -1,14 +1,14 @@ #[inline] -pub(crate) fn _get_currency_code() -> String { - String::from("") +pub(crate) fn get_currency_code() -> String { + String::new() } #[inline] -pub(crate) fn _get_decimal_separator() -> String { +pub(crate) fn get_decimal_separator() -> String { String::from(".") } #[inline] -pub(crate) fn _get_thousands_separator() -> String { +pub(crate) fn get_thousands_separator() -> String { String::from(",") } diff --git a/src/helper/utils.rs b/src/helper/utils.rs index f31cb943..b416cf8c 100644 --- a/src/helper/utils.rs +++ b/src/helper/utils.rs @@ -1,3 +1,21 @@ +#![allow(unused_imports)] + +/// A macro that implements the `From` trait for converting from one error type +/// to another. +/// +/// # Usage +/// ``` +/// use std::io; +/// +/// use my_error::MyErrorKind; +/// +/// from_err!(io::Error, MyError, Io); +/// +/// let io_err = io::Error::new(io::ErrorKind::Other, "An I/O error occurred"); +/// let my_err: MyError = io_err.into(); +/// +/// assert_eq!(my_err.kind(), MyErrorKind::Io); +/// ``` #[macro_export] macro_rules! from_err { ($from:ty, $to:tt, $var:tt) => { @@ -9,3 +27,109 @@ macro_rules! from_err { } }; } + +/// Asserts that the SHA-256 hash of a given input matches the expected +/// hexadecimal string. +/// +/// # Arguments +/// +/// * `$input` - The input data to hash. +/// * `$expected_hex` - The expected SHA-256 hash as a hexadecimal string. +/// +/// # Panics +/// +/// This macro will panic if the actual SHA-256 hash does not match the +/// expected hash. +/// +/// # Examples +/// +/// ```ignore +/// let data = b"Hello, world!"; +/// assert_sha256!( +/// data, +/// "a591a6d40bf420404a011733cfb7b190d62c65bf0bcda32b57b277d9ad9f146e" +/// ); +/// // This will not panic +/// +/// assert_sha256!(data, "invalid_hash"); +/// // This will panic with a message indicating the mismatch +/// ``` +macro_rules! assert_sha256 { + ($input:expr, $expected_hex:expr) => {{ + let hash = Sha256::digest($input).to_vec(); + let expected_bytes = hex_literal::hex!($expected_hex); + assert_eq!( + &hash, + &expected_bytes, + "SHA256({}) mismatch! Expected: {:?}, Actual: {:?}", + stringify!($input), + &expected_bytes + .iter() + .map(|b| format!("{:02x}", b)) + .collect::(), + &hash + .iter() + .map(|b| format!("{:02x}", b)) + .collect::() + ); + }}; +} + +/// A macro that compiles a regular expression and caches it. +/// +/// # Usage +/// ``` +/// let re = compile_regex!(r"^\d+$"); +/// +/// assert!(re.is_match("123").unwrap()); +/// assert!(!re.is_match("abc").unwrap()); +/// ``` +macro_rules! compile_regex { + ($re:literal $(,)?) => {{ + static RE: std::sync::OnceLock = std::sync::OnceLock::new(); + RE.get_or_init(|| fancy_regex::Regex::new($re).unwrap()) + }}; +} + +/// Prints a byte slice as a hex string, prefixed with the variable name. +/// +/// This macro takes a reference to a byte slice (`&[u8]`) and prints both +/// the variable name and its hexadecimal representation to stdout. +macro_rules! print_hex { + ($var:expr) => { + println!( + "{} = {}", + stringify!($var), + $var.iter() + .map(|b| format!("{:02x}", b)) + .collect::() + ); + }; +} + +/// Prints the SHA-256 hash of a given input as a hexadecimal string. +/// +/// # Examples +/// +/// ```ignore +/// let data = b"Hello, world!"; +/// print_sha256_hex!(data); +/// // Output: SHA256(data) = 5eb63bbbe01eeed093cb22bb8f5acdc3 +/// ``` +macro_rules! print_sha256_hex { + ($var:expr) => { + let hash = Sha256::digest($var); + println!( + "SHA256({}) = {}", + stringify!($var), + hash.iter() + .map(|b| format!("{:02x}", b)) + .collect::() + ); + }; +} + +pub(crate) use assert_sha256; +pub(crate) use compile_regex; +pub(crate) use print_hex; +pub(crate) use print_sha256_hex; diff --git a/src/lib.rs b/src/lib.rs index 4b391c84..947d3e5e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,131 +1,225 @@ +//! # umya-spreadsheet +//! +//! A pure `rust` library for reading and writing Microsoft Excel (xlsx) files. +//! //! ## Example +//! //! ![Result Image](https://github.com/MathNya/umya-spreadsheet/raw/master/images/sample1.png) +//! //! ### Reader or New File +//! //! ```rust //! use umya_spreadsheet::*; //! -//! // reader +//! // Reader //! let path = std::path::Path::new("./tests/test_files/aaa.xlsx"); //! let mut book = reader::xlsx::read(path).unwrap(); -//! // or -//! // lazy reader -//! // Delays the loading of the worksheet until it is needed.//! // When loading a file with a large amount of data, response improvement can be expected. +//! +//! // Lazy Reader +//! // Delays loading of worksheets until they are needed. +//! // Can improve performance when loading large files. //! let path = std::path::Path::new("./tests/test_files/aaa.xlsx"); //! let mut book = reader::xlsx::lazy_read(path).unwrap(); -//! // or -//! // new file +//! +//! // New file //! let mut book = new_file(); //! ``` -//! ### New worksheet +//! +//! ### New Worksheet +//! //! ```rust //! use umya_spreadsheet::*; +//! //! let mut book = new_file(); //! -//! // new worksheet -//! let _ = book.new_sheet("Sheet2"); +//! // New worksheet +//! let _unused = book.new_sheet("Sheet2"); //! ``` -//! ### Copy worksheet +//! +//! ### Copy Worksheet +//! //! ```rust //! use umya_spreadsheet::*; +//! //! let mut book = new_file(); //! -//! let mut clone_sheet = book.get_sheet(&0).unwrap().clone(); +//! let mut clone_sheet = book.get_sheet(0).unwrap().clone(); //! clone_sheet.set_name("New Sheet"); -//! let _ = book.add_sheet(clone_sheet); +//! let _unused = book.add_sheet(clone_sheet); //! ``` -//! ### Change value +//! +//! ### Change Value +//! //! ```rust //! use umya_spreadsheet::*; +//! //! let mut book = new_file(); -//! let _ = book.new_sheet("Sheet2"); -//! -//! // change value -//! book.get_sheet_by_name_mut("Sheet2").unwrap().get_cell_mut("A1").set_value("TEST1"); -//! book.get_sheet_by_name_mut("Sheet2").unwrap().get_cell_mut("B2").set_value_from_i32(1); -//! book.get_sheet_by_name_mut("Sheet2").unwrap().get_cell_mut("C3").set_value_from_bool(true); -//! // or -//! book.get_sheet_mut(&1).unwrap().get_cell_mut((1, 1)).set_value("TEST1"); -//! book.get_sheet_mut(&1).unwrap().get_cell_mut((2, 2)).set_value_from_i32(1)); -//! book.get_sheet_mut(&1).unwrap().get_cell_mut((3, 3)).set_value_from_bool(true)); +//! let _unused = book.new_sheet("Sheet2"); +//! +//! // Change value using string cell address +//! book.get_sheet_by_name_mut("Sheet2") +//! .unwrap() +//! .get_cell_mut("A1") +//! .set_value("TEST1"); +//! book.get_sheet_by_name_mut("Sheet2") +//! .unwrap() +//! .get_cell_mut("B2") +//! .set_value_from_i32(1); +//! book.get_sheet_by_name_mut("Sheet2") +//! .unwrap() +//! .get_cell_mut("C3") +//! .set_value_from_bool(true); +//! +//! // Change value using tuple cell address +//! book.get_sheet_mut(1) +//! .unwrap() +//! .get_cell_mut((1, 1)) +//! .set_value("TEST1"); +//! book.get_sheet_mut(1) +//! .unwrap() +//! .get_cell_mut((2, 2)) +//! .set_value_from_i32(1); +//! book.get_sheet_mut(1) +//! .unwrap() +//! .get_cell_mut((3, 3)) +//! .set_value_from_bool(true); //! ``` -//! ### Read value +//! +//! ### Read Value +//! //! ```rust //! use umya_spreadsheet::*; +//! //! let mut book = new_file(); -//! let _ = book.new_sheet("Sheet2"); -//! book.get_sheet_by_name_mut("Sheet2").unwrap().get_cell_mut("A1").set_value("TEST1"); +//! let _unused = book.new_sheet("Sheet2"); +//! book.get_sheet_by_name_mut("Sheet2") +//! .unwrap() +//! .get_cell_mut("A1") +//! .set_value("TEST1"); //! -//! // read value +//! // Read value by string cell address //! let a1_value = book.get_sheet_by_name("Sheet2").unwrap().get_value("A1"); -//! // or -//! let a1_value = book.get_sheet(&1).unwrap().get_value((1, 1)); -//! // or formatted value -//! let a1_value = book.get_sheet(&1).unwrap().get_formatted_value("A1"); -//! assert_eq!("TEST1", a1_value); // TEST1 +//! +//! // Read value by tuple cell address +//! let a1_value = book.get_sheet(1).unwrap().get_value((1, 1)); +//! +//! // Read formatted value by string cell address +//! let a1_value = book.get_sheet(1).unwrap().get_formatted_value("A1"); +//! +//! assert_eq!("TEST1", a1_value); //! ``` -//! ### Change style -//! more example is [**here**](Style). +//! +//! ### Change Style +//! +//! More examples can be found in the [Style](crate::structs::style) module. +//! //! ```rust //! use umya_spreadsheet::*; +//! //! let mut book = new_file(); -//! let _ = book.new_sheet("Sheet2"); -//! -//! // add bottom border -//! book.get_sheet_by_name_mut("Sheet2").unwrap() -//! .get_style_mut("A1") -//! .get_borders_mut() -//! .get_bottom_mut() -//! .set_border_style(Border::BORDER_MEDIUM); -//! // or -//! book.get_sheet_mut(&1).unwrap() -//! .get_style_mut((1, 1)) -//! .get_borders_mut() -//! .get_bottom_mut() -//! .set_border_style(Border::BORDER_MEDIUM); +//! let _unused = book.new_sheet("Sheet2"); +//! +//! // Add a bottom border using string cell address +//! book.get_sheet_by_name_mut("Sheet2") +//! .unwrap() +//! .get_style_mut("A1") +//! .get_borders_mut() +//! .get_bottom_mut() +//! .set_border_style(Border::BORDER_MEDIUM); +//! +//! // Add a bottom border using tuple cell address +//! book.get_sheet_mut(1) +//! .unwrap() +//! .get_style_mut((1, 1)) +//! .get_borders_mut() +//! .get_bottom_mut() +//! .set_border_style(Border::BORDER_MEDIUM); //! ``` -//! ### Insert or Remove Rows(or Columns) +//! +//! ### Insert or Remove Rows/Columns +//! //! ![Result Image](https://github.com/MathNya/umya-spreadsheet/raw/master/images/sample2.png) +//! //! ```rust //! use umya_spreadsheet::*; +//! //! let mut book = new_file(); //! -//! // insert rows +//! // Insert rows //! book.insert_new_row("Sheet1", &2, &3); //! -//! // insert columns +//! // Insert columns by column name //! book.insert_new_column("Sheet1", "B", &3); -//! // or +//! +//! // Insert columns by index //! book.insert_new_column_by_index("Sheet1", &2, &3); //! -//! // remove rows +//! // Remove rows //! book.remove_row("Sheet1", &6, &2); //! -//! // remove columns +//! // Remove columns by column name //! book.remove_column("Sheet1", "F", &2); -//! // or +//! +//! // Remove columns by index //! book.remove_column_by_index("Sheet1", &6, &2); //! ``` +//! //! ### Writer +//! //! ```rust //! use umya_spreadsheet::*; +//! //! let mut book = new_file(); -//! let _ = book.new_sheet("Sheet2"); +//! let _unused = book.new_sheet("Sheet2"); //! -//! // writer +//! // Write to a file //! let path = std::path::Path::new("C:/spread_test_data/ccc.xlsx"); -//! let _ = writer::xlsx::write(&book, path); +//! let _unused = writer::xlsx::write(&book, path); //! ``` -#![allow(warnings)] -#![allow(clippy::all)] +#![deny( + explicit_outlives_requirements, + let_underscore_drop, + meta_variable_misuse, + non_ascii_idents, + non_local_definitions, + redundant_imports, + redundant_lifetimes, + single_use_lifetimes, + trivial_casts, + trivial_numeric_casts, + unit_bindings, + unsafe_code, + unused_import_braces, + unused_lifetimes, + unused_macro_rules, + unused_qualifications, + variant_size_differences +)] +#![allow(dead_code, unused_macros)] +#![deny(clippy::correctness, clippy::trivially_copy_pass_by_ref)] +#![warn( + clippy::style, + clippy::complexity, + clippy::perf, + clippy::pedantic, + clippy::cargo, + clippy::suspicious +)] +#![allow( + clippy::missing_errors_doc, + clippy::missing_panics_doc, + clippy::module_name_repetitions, + clippy::similar_names, + clippy::too_many_lines, + clippy::struct_field_names +)] extern crate chrono; extern crate fancy_regex; -#[cfg(feature = "image")] -extern crate image; +extern crate imagesize; extern crate md5; extern crate quick_xml; -extern crate thin_vec; extern crate thousands; extern crate zip; @@ -134,60 +228,70 @@ extern crate base64; extern crate byteorder; extern crate cbc; extern crate cfb; -extern crate getrandom; extern crate hmac; extern crate html_parser; +extern crate rand; extern crate sha2; -#[macro_use] -extern crate lazy_static; - pub mod helper; pub mod reader; pub mod structs; pub mod traits; +mod version; pub mod writer; +#[allow(unused_imports)] +pub use version::*; + pub use self::structs::*; -pub use self::traits::*; -/// create new spreadsheet file. -/// # Arguments -/// # Return value -/// * Spreadsheet structs object. -/// # Examples -/// ``` -/// let mut book = umya_spreadsheet::new_file(); -/// ``` -pub fn new_file() -> structs::Spreadsheet { - let mut spreadsheet = structs::Spreadsheet::default(); - spreadsheet.set_theme(structs::drawing::Theme::get_default_value()); - spreadsheet.set_stylesheet_defalut_value(); - let worksheet = spreadsheet.new_sheet("Sheet1").unwrap(); +/// Creates a new workbook with default settings. +/// +/// Returns a new `Workbook` instance initialized with: +/// - Default theme +/// - Default stylesheet +/// - One worksheet named "Sheet1" +/// - Active cell set to "A1" +/// - Sheet view configured with workbook view ID 0 +/// +/// # Panics +/// +/// Panics if unable to create a new worksheet named "Sheet1". This should never +/// happen with default settings since it's the first worksheet in a new +/// spreadsheet. +#[must_use] +pub fn new_file() -> Workbook { + let mut wb = Workbook::default(); + wb.set_theme(drawing::Theme::default_value()); + wb.set_stylesheet_default_value(); + let worksheet = wb.new_sheet("Sheet1").unwrap(); worksheet.set_active_cell("A1"); let mut sheet_view = SheetView::default(); sheet_view.set_workbook_view_id(0); let mut sheet_views = SheetViews::default(); sheet_views.add_sheet_view_list_mut(sheet_view); worksheet.set_sheets_views(sheet_views); - spreadsheet.set_active_sheet(0); - spreadsheet + wb.set_active_sheet(0); + wb } -/// create new spreadsheet file. -/// not include worksheet. -/// At least one additional worksheet must be added before the correct file can be generated. +/// Creates a new empty workbook without any worksheets. +/// +/// This function initializes a new workbook with default theme and +/// stylesheet settings. At least one worksheet must be added before generating +/// a valid file. +/// +/// # Returns +/// A new `Workbook` instance with default configuration but no worksheets. /// -/// # Arguments -/// # Return value -/// * Spreadsheet structs object. /// # Examples /// ``` /// let mut book = umya_spreadsheet::new_file_empty_worksheet(); /// ``` -pub fn new_file_empty_worksheet() -> structs::Spreadsheet { - let mut spreadsheet = structs::Spreadsheet::default(); - spreadsheet.set_theme(structs::drawing::Theme::get_default_value()); - spreadsheet.set_stylesheet_defalut_value(); - spreadsheet +#[must_use] +pub fn new_file_empty_worksheet() -> Workbook { + let mut wb = Workbook::default(); + wb.set_theme(drawing::Theme::default_value()); + wb.set_stylesheet_default_value(); + wb } diff --git a/src/reader/driver.rs b/src/reader/driver.rs index 6f41b3f1..53be490a 100644 --- a/src/reader/driver.rs +++ b/src/reader/driver.rs @@ -1,6 +1,13 @@ +use std::{ + path::{ + Component, + Path, + PathBuf, + }, + string::FromUtf8Error, +}; + use quick_xml::events::attributes::Attribute; -use std::path::{Component, Path, PathBuf}; -use std::string::FromUtf8Error; #[macro_export] macro_rules! xml_read_loop { @@ -38,7 +45,7 @@ pub(crate) use crate::set_string_from_xml; pub(crate) fn normalize_path(path: &str) -> PathBuf { let path = Path::new(path); let mut components = path.components().peekable(); - let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() { + let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().copied() { components.next(); PathBuf::from(c.as_os_str()) } else { @@ -67,7 +74,7 @@ pub(crate) fn normalize_path(path: &str) -> PathBuf { pub(crate) fn join_paths(base_path: &str, target: &str) -> String { match target.split_once('/') { Some(("", target)) => normalize_path_to_str(target), - _ => normalize_path_to_str(&format!("{}/{}", base_path, target)), + _ => normalize_path_to_str(&format!("{base_path}/{target}")), } } diff --git a/src/reader/xlsx.rs b/src/reader/xlsx.rs index e9f93296..a173dbac 100644 --- a/src/reader/xlsx.rs +++ b/src/reader/xlsx.rs @@ -1,20 +1,32 @@ -use std::fmt; -use std::fs::File; -use std::io; -use std::path::Path; -use std::string::FromUtf8Error; -use std::sync::Arc; -use std::sync::RwLock; +use std::{ + fs::File, + io, + path::Path, + sync::RwLock, +}; use super::driver; -use crate::helper::const_str::*; -use crate::structs::drawing::Theme; -use crate::structs::raw::RawWorksheet; -use crate::structs::SharedStringTable; -use crate::structs::Spreadsheet; -use crate::structs::Stylesheet; -use crate::structs::Worksheet; -use crate::XlsxError; +use crate::{ + XlsxError, + helper::const_str::{ + COMMENTS_NS, + DRAWINGS_NS, + PIVOT_CACHE_DEF_NS, + PIVOT_TABLE_NS, + TABLE_NS, + THEME_NS, + THREADED_COMMENT_NS, + VML_DRAWING_NS, + }, + structs::{ + SharedStringTable, + Stylesheet, + Workbook, + Worksheet, + drawing::Theme, + raw::RawWorksheet, + }, +}; pub(crate) mod chart; pub(crate) mod comment; @@ -23,12 +35,15 @@ mod doc_props_app; mod doc_props_core; mod doc_props_custom; pub(crate) mod drawing; +mod jsa_project_bin; +mod pivot_cache; mod pivot_table; mod rels; mod shared_strings; mod styles; pub(crate) mod table; mod theme; +pub(crate) mod threaded_comment; mod vba_project_bin; pub(crate) mod vml_drawing; mod workbook; @@ -39,11 +54,11 @@ pub(crate) mod worksheet; /// # Arguments /// * `reader` - reader to read from. /// # Return value -/// * `Result` - OK is Spreadsheet. Err is error message. +/// * `Result` - OK is `Workbook`. Err is error message. pub fn read_reader( reader: R, with_sheet_read: bool, -) -> Result { +) -> Result { let mut arv = zip::read::ZipArchive::new(reader)?; let mut book = workbook::read(&mut arv)?; @@ -51,10 +66,11 @@ pub fn read_reader( doc_props_core::read(&mut arv, &mut book)?; doc_props_custom::read(&mut arv, &mut book)?; vba_project_bin::read(&mut arv, &mut book)?; + jsa_project_bin::read(&mut arv, &mut book)?; content_types::read(&mut arv, &mut book)?; let workbook_rel = workbook_rels::read(&mut arv, &mut book)?; - book.set_theme(Theme::get_default_value()); + book.set_theme(Theme::default_value()); for (_, type_value, rel_target) in &workbook_rel { if type_value == THEME_NS { let theme = theme::read(&mut arv, rel_target)?; @@ -65,9 +81,9 @@ pub fn read_reader( shared_strings::read(&mut arv, &mut book)?; styles::read(&mut arv, &mut book)?; - for sheet in book.get_sheet_collection_mut() { + for sheet in book.sheet_collection_mut() { for (rel_id, _, rel_target) in &workbook_rel { - if sheet.get_r_id() != rel_id { + if sheet.r_id() != rel_id { continue; } let mut raw_worksheet = RawWorksheet::default(); @@ -87,46 +103,46 @@ pub fn read_reader( /// # Arguments /// * `path` - file path to read. /// # Return value -/// * `Result` - OK is Spreadsheet. Err is error message. +/// * `Result` - OK is Workbook. Err is error message. /// # Examples /// ``` /// let path = std::path::Path::new("./tests/test_files/aaa.xlsx"); /// let mut book = umya_spreadsheet::reader::xlsx::read(path).unwrap(); /// ``` #[inline] -pub fn read>(path: P) -> Result { +pub fn read>(path: P) -> Result { let file = File::open(path)?; read_reader(file, true) } /// lazy read spreadsheet file. /// Delays the loading of the worksheet until it is needed. -/// When loading a file with a large amount of data, response improvement can be expected. -/// # Arguments +/// When loading a file with a large amount of data, response improvement can be +/// expected. # Arguments /// * `path` - file path to read. /// # Return value -/// * `Result` - OK is Spreadsheet. Err is error message. +/// * `Result` - OK is Workbook. Err is error message. /// # Examples /// ``` /// let path = std::path::Path::new("./tests/test_files/aaa.xlsx"); /// let mut book = umya_spreadsheet::reader::xlsx::lazy_read(path).unwrap(); /// ``` #[inline] -pub fn lazy_read(path: &Path) -> Result { +pub fn lazy_read(path: &Path) -> Result { let file = File::open(path)?; read_reader(file, false) } pub(crate) fn raw_to_deserialize_by_worksheet( worksheet: &mut Worksheet, - shared_string_table: Arc>, + shared_string_table: &RwLock, stylesheet: &Stylesheet, ) { if worksheet.is_deserialized() { return; } - let raw_data_of_worksheet = worksheet.get_raw_data_of_worksheet().clone(); + let raw_data_of_worksheet = worksheet.raw_data_of_worksheet().clone(); let shared_string_table = &*shared_string_table.read().unwrap(); worksheet::read( worksheet, @@ -136,38 +152,48 @@ pub(crate) fn raw_to_deserialize_by_worksheet( ) .unwrap(); - if let Some(v) = raw_data_of_worksheet.get_worksheet_relationships() { - for relationship in v.get_relationship_list() { + if let Some(v) = raw_data_of_worksheet.worksheet_relationships() { + for relationship in v.relationship_list() { match relationship.get_type() { // drawing, chart DRAWINGS_NS => { drawing::read( worksheet, - relationship.get_raw_file(), - raw_data_of_worksheet.get_drawing_relationships(), - ) - .unwrap(); + relationship.raw_file(), + raw_data_of_worksheet.drawing_relationships(), + ); } // comment COMMENTS_NS => { - comment::read(worksheet, relationship.get_raw_file()).unwrap(); + comment::read(worksheet, relationship.raw_file()); + } + // threaded_comment + THREADED_COMMENT_NS => { + threaded_comment::read(worksheet, relationship.raw_file()); } // table TABLE_NS => { - table::read(worksheet, relationship.get_raw_file()).unwrap(); + table::read(worksheet, relationship.raw_file()).unwrap(); + } + // pivot table + PIVOT_TABLE_NS => { + pivot_table::read(worksheet, relationship.raw_file()); + } + // pivot cache + PIVOT_CACHE_DEF_NS => { + pivot_cache::read(worksheet, relationship.raw_file()); } _ => {} } } - for relationship in v.get_relationship_list() { + for relationship in v.relationship_list() { // vmlDrawing if relationship.get_type() == VML_DRAWING_NS { vml_drawing::read( worksheet, - relationship.get_raw_file(), - raw_data_of_worksheet.get_vml_drawing_relationships(), - ) - .unwrap(); + relationship.raw_file(), + raw_data_of_worksheet.vml_drawing_relationships(), + ); } } } diff --git a/src/reader/xlsx/chart.rs b/src/reader/xlsx/chart.rs index 2d47ba74..969c839f 100644 --- a/src/reader/xlsx/chart.rs +++ b/src/reader/xlsx/chart.rs @@ -1,16 +1,18 @@ -use super::XlsxError; -use crate::structs::drawing::charts::ChartSpace; -use crate::structs::raw::RawFile; -use crate::xml_read_loop; -use quick_xml::events::Event; -use quick_xml::Reader; -use std::result; +use quick_xml::{ + Reader, + events::Event, +}; -pub(crate) fn read( - raw_file: &RawFile, - chart_space: &mut ChartSpace, -) -> result::Result<(), XlsxError> { - let data = std::io::Cursor::new(raw_file.get_file_data()); +use crate::{ + structs::{ + drawing::charts::ChartSpace, + raw::RawFile, + }, + xml_read_loop, +}; + +pub(crate) fn read(raw_file: &RawFile, chart_space: &mut ChartSpace) { + let data = std::io::Cursor::new(raw_file.file_data()); let mut reader = Reader::from_reader(data); reader.config_mut().trim_text(true); @@ -24,6 +26,4 @@ pub(crate) fn read( }, Event::Eof => break, ); - - Ok(()) } diff --git a/src/reader/xlsx/comment.rs b/src/reader/xlsx/comment.rs index 7145e3a8..5e9e98c7 100644 --- a/src/reader/xlsx/comment.rs +++ b/src/reader/xlsx/comment.rs @@ -1,27 +1,29 @@ -use super::XlsxError; -use crate::structs::raw::RawFile; -use crate::structs::Comment; -use crate::structs::Worksheet; -use crate::xml_read_loop; -use quick_xml::events::Event; -use quick_xml::Reader; -use std::result; +use quick_xml::{ + Reader, + events::Event, +}; -pub(crate) fn read( - worksheet: &mut Worksheet, - drawing_file: &RawFile, -) -> result::Result<(), XlsxError> { - let data = std::io::Cursor::new(drawing_file.get_file_data()); +use crate::{ + structs::{ + Comment, + Worksheet, + raw::RawFile, + }, + xml_read_loop, +}; + +pub(crate) fn read(worksheet: &mut Worksheet, drawing_file: &RawFile) { + let data = std::io::Cursor::new(drawing_file.file_data()); let mut reader = Reader::from_reader(data); reader.config_mut().trim_text(false); let mut authors: Vec = Vec::new(); - let mut value: String = String::from(""); + let mut value: String = String::new(); xml_read_loop!( reader, Event::Empty(ref e) => { if e.name().into_inner() == b"author" { - authors.push(String::from("")); + authors.push(String::new()); } }, Event::Start(ref e) => { @@ -41,6 +43,4 @@ pub(crate) fn read( }, Event::Eof => break, ); - - Ok(()) } diff --git a/src/reader/xlsx/content_types.rs b/src/reader/xlsx/content_types.rs index 197261ac..9db23c87 100644 --- a/src/reader/xlsx/content_types.rs +++ b/src/reader/xlsx/content_types.rs @@ -1,15 +1,26 @@ -use super::driver::*; -use super::XlsxError; -use crate::helper::const_str::*; -use crate::structs::Spreadsheet; -use quick_xml::events::Event; -use quick_xml::Reader; -use std::{io, result}; +use std::io; + +use quick_xml::{ + Reader, + events::Event, +}; + +use super::{ + XlsxError, + driver::{ + get_attribute, + xml_read_loop, + }, +}; +use crate::{ + helper::const_str::CONTENT_TYPES, + structs::Workbook, +}; pub(crate) fn read( arv: &mut zip::ZipArchive, - spreadsheet: &mut Spreadsheet, -) -> result::Result<(), XlsxError> { + wb: &mut Workbook, +) -> Result<(), XlsxError> { let r = io::BufReader::new(arv.by_name(CONTENT_TYPES)?); let mut reader = Reader::from_reader(r); reader.config_mut().trim_text(true); @@ -27,6 +38,6 @@ pub(crate) fn read( Event::Eof => break, ); - spreadsheet.set_backup_context_types(list); + wb.set_backup_context_types(list); Ok(()) } diff --git a/src/reader/xlsx/doc_props_app.rs b/src/reader/xlsx/doc_props_app.rs index ad8a9263..475a5a7d 100644 --- a/src/reader/xlsx/doc_props_app.rs +++ b/src/reader/xlsx/doc_props_app.rs @@ -1,15 +1,20 @@ -use super::XlsxError; -use crate::helper::const_str::*; -use quick_xml::events::Event; -use quick_xml::Reader; -use std::{io, result}; +use std::io; + +use quick_xml::{ + Reader, + events::Event, +}; -use crate::structs::Spreadsheet; +use super::XlsxError; +use crate::{ + helper::const_str::ARC_APP, + structs::Workbook, +}; pub(crate) fn read( arv: &mut zip::ZipArchive, - spreadsheet: &mut Spreadsheet, -) -> result::Result<(), XlsxError> { + wb: &mut Workbook, +) -> Result<(), XlsxError> { let r = io::BufReader::new(match arv.by_name(ARC_APP) { Ok(v) => v, Err(zip::result::ZipError::FileNotFound) => { @@ -26,9 +31,7 @@ pub(crate) fn read( match reader.read_event_into(&mut buf) { Ok(Event::Start(ref e)) => { if e.name().into_inner() == b"Properties" { - spreadsheet - .get_properties_mut() - .set_attributes_app(&mut reader, e); + wb.properties_mut().set_attributes_app(&mut reader, e); } } Ok(Event::Eof) => break, diff --git a/src/reader/xlsx/doc_props_core.rs b/src/reader/xlsx/doc_props_core.rs index 37bdd699..1b51c0fb 100644 --- a/src/reader/xlsx/doc_props_core.rs +++ b/src/reader/xlsx/doc_props_core.rs @@ -1,15 +1,20 @@ -use super::XlsxError; -use quick_xml::events::Event; -use quick_xml::Reader; -use std::{io, result}; +use std::io; + +use quick_xml::{ + Reader, + events::Event, +}; -use crate::helper::const_str::*; -use crate::structs::Spreadsheet; +use super::XlsxError; +use crate::{ + helper::const_str::ARC_CORE, + structs::Workbook, +}; pub(crate) fn read( arv: &mut zip::ZipArchive, - spreadsheet: &mut Spreadsheet, -) -> result::Result<(), XlsxError> { + wb: &mut Workbook, +) -> Result<(), XlsxError> { let r = io::BufReader::new(match arv.by_name(ARC_CORE) { Ok(v) => v, Err(zip::result::ZipError::FileNotFound) => { @@ -26,9 +31,7 @@ pub(crate) fn read( match reader.read_event_into(&mut buf) { Ok(Event::Start(ref e)) => { if e.name().into_inner() == b"cp:coreProperties" { - spreadsheet - .get_properties_mut() - .set_attributes_core(&mut reader, e); + wb.properties_mut().set_attributes_core(&mut reader, e); } } Ok(Event::Eof) => break, diff --git a/src/reader/xlsx/doc_props_custom.rs b/src/reader/xlsx/doc_props_custom.rs index 28d3e184..225690da 100644 --- a/src/reader/xlsx/doc_props_custom.rs +++ b/src/reader/xlsx/doc_props_custom.rs @@ -1,15 +1,20 @@ -use super::XlsxError; -use quick_xml::events::Event; -use quick_xml::Reader; -use std::{io, result}; +use std::io; + +use quick_xml::{ + Reader, + events::Event, +}; -use crate::helper::const_str::*; -use crate::structs::Spreadsheet; +use super::XlsxError; +use crate::{ + helper::const_str::ARC_CUSTOM, + structs::Workbook, +}; pub(crate) fn read( arv: &mut zip::ZipArchive, - spreadsheet: &mut Spreadsheet, -) -> result::Result<(), XlsxError> { + wb: &mut Workbook, +) -> Result<(), XlsxError> { let r = io::BufReader::new(match arv.by_name(ARC_CUSTOM) { Ok(v) => v, Err(zip::result::ZipError::FileNotFound) => { @@ -26,9 +31,7 @@ pub(crate) fn read( match reader.read_event_into(&mut buf) { Ok(Event::Start(ref e)) => { if e.name().into_inner() == b"Properties" { - spreadsheet - .get_properties_mut() - .set_attributes_custom(&mut reader, e); + wb.properties_mut().set_attributes_custom(&mut reader, e); } } Ok(Event::Eof) => break, diff --git a/src/reader/xlsx/drawing.rs b/src/reader/xlsx/drawing.rs index 107a3185..538e4e2a 100644 --- a/src/reader/xlsx/drawing.rs +++ b/src/reader/xlsx/drawing.rs @@ -1,19 +1,26 @@ -use super::XlsxError; -use crate::reader::driver::xml_read_loop; -use crate::structs::drawing::spreadsheet::WorksheetDrawing; -use crate::structs::raw::RawFile; -use crate::structs::raw::RawRelationships; -use crate::structs::Worksheet; -use quick_xml::events::Event; -use quick_xml::Reader; -use std::result; +use quick_xml::{ + Reader, + events::Event, +}; + +use crate::{ + reader::driver::xml_read_loop, + structs::{ + Worksheet, + drawing::spreadsheet::WorksheetDrawing, + raw::{ + RawFile, + RawRelationships, + }, + }, +}; pub(crate) fn read( worksheet: &mut Worksheet, drawing_file: &RawFile, drawing_relationships: Option<&RawRelationships>, -) -> result::Result<(), XlsxError> { - let data = std::io::Cursor::new(drawing_file.get_file_data()); +) { + let data = std::io::Cursor::new(drawing_file.file_data()); let mut reader = Reader::from_reader(data); reader.config_mut().trim_text(true); @@ -26,13 +33,11 @@ pub(crate) fn read( &mut reader, e, drawing_relationships, - worksheet.get_ole_objects_mut(), + worksheet.ole_objects_mut(), ); worksheet.set_worksheet_drawing(obj); } }, Event::Eof => break ); - - Ok(()) } diff --git a/src/reader/xlsx/jsa_project_bin.rs b/src/reader/xlsx/jsa_project_bin.rs new file mode 100644 index 00000000..144079fc --- /dev/null +++ b/src/reader/xlsx/jsa_project_bin.rs @@ -0,0 +1,31 @@ +use std::{ + io, + io::Read, +}; + +use super::XlsxError; +use crate::{ + helper::const_str::PKG_JSA_PROJECT, + structs::Workbook, +}; + +pub(crate) fn read( + arv: &mut zip::ZipArchive, + wb: &mut Workbook, +) -> Result<(), XlsxError> { + let mut r = io::BufReader::new(match arv.by_name(PKG_JSA_PROJECT) { + Ok(v) => v, + Err(zip::result::ZipError::FileNotFound) => { + return Ok(()); + } + Err(e) => { + return Err(e.into()); + } + }); + let mut buf = Vec::new(); + r.read_to_end(&mut buf)?; + + wb.set_jsa_macros_code(buf); + + Ok(()) +} diff --git a/src/reader/xlsx/pivot_cache.rs b/src/reader/xlsx/pivot_cache.rs new file mode 100644 index 00000000..b402727c --- /dev/null +++ b/src/reader/xlsx/pivot_cache.rs @@ -0,0 +1,50 @@ +use quick_xml::{ + Reader, + events::Event, +}; + +use crate::structs::{ + PivotCacheDefinition, + Worksheet, + raw::RawFile, +}; + +pub(crate) fn read(worksheet: &mut Worksheet, pivot_cache_file: &RawFile) { + let data = std::io::Cursor::new(pivot_cache_file.file_data()); + let mut reader = Reader::from_reader(data); + reader.config_mut().trim_text(false); + let mut buf = Vec::new(); + let mut pivot_cache_def = PivotCacheDefinition::default(); + + loop { + match reader.read_event_into(&mut buf) { + Ok(Event::Start(ref e) | Event::Empty(ref e)) => match e.name().into_inner() { + // Support both element names for backwards compatibility with incorrectly written + // files + b"pivotCacheDefinition" | b"pivotTableDefinition" => { + pivot_cache_def.set_attributes(&mut reader, e); + } + _ => (), + }, + Ok(Event::End(ref e)) => match e.name().into_inner() { + b"pivotCacheDefinition" | b"pivotTableDefinition" => { + break; + } + _ => (), + }, + Ok(Event::Eof) => break, + Err(e) => panic!("Error at position {}: {:?}", reader.buffer_position(), e), + _ => (), + } + buf.clear(); + } + + dbg!(pivot_cache_def.id()); + + // Associate the cache definition with pivot tables that use this cache_id + for pivot_table in worksheet.pivot_tables_mut() { + if pivot_table.pivot_table_definition().cache_id() == 1 { + pivot_table.set_pivot_cache_definition(pivot_cache_def.clone()); + } + } +} diff --git a/src/reader/xlsx/pivot_table.rs b/src/reader/xlsx/pivot_table.rs index 5e955b14..c48f0a3c 100644 --- a/src/reader/xlsx/pivot_table.rs +++ b/src/reader/xlsx/pivot_table.rs @@ -1,38 +1,35 @@ -use super::driver::*; -use super::XlsxError; -use crate::structs::raw::RawFile; -use crate::structs::PivotTable; -use crate::structs::PivotTableDefinition; -use crate::structs::Worksheet; -use quick_xml::events::Event; -use quick_xml::Reader; -use std::result; +use quick_xml::{ + Reader, + events::Event, +}; -pub(crate) fn read( - worksheet: &mut Worksheet, - pivot_table_file: &RawFile, -) -> result::Result<(), XlsxError> { - let data = std::io::Cursor::new(pivot_table_file.get_file_data()); +use crate::structs::{ + PivotTable, + PivotTableDefinition, + Worksheet, + raw::RawFile, +}; + +pub(crate) fn read(worksheet: &mut Worksheet, pivot_table_file: &RawFile) { + let data = std::io::Cursor::new(pivot_table_file.file_data()); let mut reader = Reader::from_reader(data); reader.config_mut().trim_text(false); let mut buf = Vec::new(); let mut pivot_table = PivotTable::default(); loop { match reader.read_event_into(&mut buf) { - Ok(Event::Start(ref e)) => match e.name().into_inner() { - b"pivotTableDefinition" => { + Ok(Event::Start(ref e)) => { + if e.name().into_inner() == b"pivotTableDefinition" { let mut obj = PivotTableDefinition::default(); obj.set_attributes(&mut reader, e); pivot_table.set_pivot_table_definition(obj); } - _ => (), - }, - Ok(Event::End(ref e)) => match e.name().into_inner() { - b"pivotTableDefinition" => { + } + Ok(Event::End(ref e)) => { + if e.name().into_inner() == b"pivotTableDefinition" { break; } - _ => (), - }, + } Ok(Event::Eof) => break, Err(e) => panic!("Error at position {}: {:?}", reader.buffer_position(), e), _ => (), @@ -40,5 +37,4 @@ pub(crate) fn read( buf.clear(); } worksheet.add_pivot_table(pivot_table); - Ok(()) } diff --git a/src/reader/xlsx/shared_strings.rs b/src/reader/xlsx/shared_strings.rs index 03ea1e8e..9f8a8f3e 100644 --- a/src/reader/xlsx/shared_strings.rs +++ b/src/reader/xlsx/shared_strings.rs @@ -1,17 +1,24 @@ -use crate::xml_read_loop; +use std::io; + +use quick_xml::{ + Reader, + events::Event, +}; use super::XlsxError; -use crate::helper::const_str::*; -use crate::structs::SharedStringTable; -use crate::structs::Spreadsheet; -use quick_xml::events::Event; -use quick_xml::Reader; -use std::{io, result}; +use crate::{ + helper::const_str::PKG_SHARED_STRINGS, + structs::{ + SharedStringTable, + Workbook, + }, + xml_read_loop, +}; pub(crate) fn read( arv: &mut zip::ZipArchive, - spreadsheet: &mut Spreadsheet, -) -> result::Result<(), XlsxError> { + wb: &mut Workbook, +) -> Result<(), XlsxError> { let r = io::BufReader::new(match arv.by_name(PKG_SHARED_STRINGS) { Ok(v) => v, Err(zip::result::ZipError::FileNotFound) => { @@ -30,7 +37,7 @@ pub(crate) fn read( if e.name().into_inner() == b"sst" { let mut obj = SharedStringTable::default(); obj.set_attributes(&mut reader, e); - spreadsheet.set_shared_string_table(obj); + wb.set_shared_string_table(obj); } }, Event::Eof => break, diff --git a/src/reader/xlsx/styles.rs b/src/reader/xlsx/styles.rs index 04d75109..5d813b9c 100644 --- a/src/reader/xlsx/styles.rs +++ b/src/reader/xlsx/styles.rs @@ -1,23 +1,28 @@ -use crate::xml_read_loop; +use std::io; + +use quick_xml::{ + Reader, + events::Event, +}; use super::XlsxError; -use crate::helper::const_str::*; -use crate::structs::Spreadsheet; -use crate::structs::Stylesheet; -use quick_xml::events::Event; -use quick_xml::Reader; -use std::{io, result}; +use crate::{ + helper::const_str::PKG_STYLES, + structs::{ + Stylesheet, + Workbook, + }, + xml_read_loop, +}; pub fn read( arv: &mut zip::ZipArchive, - spreadsheet: &mut Spreadsheet, -) -> result::Result<(), XlsxError> { + wb: &mut Workbook, +) -> Result<(), XlsxError> { let r = io::BufReader::new(arv.by_name(PKG_STYLES)?); let mut reader = Reader::from_reader(r); reader.config_mut().trim_text(true); - let theme = spreadsheet.get_theme().clone(); - xml_read_loop!( reader, Event::Start(ref e) => { @@ -25,7 +30,7 @@ pub fn read( let mut obj = Stylesheet::default(); obj.set_attributes(&mut reader, e); obj.make_style(); - spreadsheet.set_stylesheet(obj); + wb.set_stylesheet(obj); } }, Event::Eof => break diff --git a/src/reader/xlsx/table.rs b/src/reader/xlsx/table.rs index 66ada7f1..c74d077c 100644 --- a/src/reader/xlsx/table.rs +++ b/src/reader/xlsx/table.rs @@ -1,88 +1,94 @@ -use super::driver::*; -use super::XlsxError; -use crate::structs::raw::RawFile; -use crate::structs::Comment; -use crate::structs::Worksheet; -use crate::structs::{Table, TableColumn, TableStyleInfo}; -use quick_xml::events::Event; -use quick_xml::Reader; -use std::result; +use quick_xml::{ + Reader, + events::Event, +}; -pub(crate) fn read( - worksheet: &mut Worksheet, - table_file: &RawFile, -) -> result::Result<(), XlsxError> { - let data = std::io::Cursor::new(table_file.get_file_data()); +use super::{ + XlsxError, + driver::get_attribute_value, +}; +use crate::structs::{ + ShowColumn, + ShowStripes, + Table, + TableColumn, + TableStyleInfo, + Worksheet, + raw::RawFile, +}; + +pub(crate) fn read(worksheet: &mut Worksheet, table_file: &RawFile) -> Result<(), XlsxError> { + let data = std::io::Cursor::new(table_file.file_data()); let mut reader = Reader::from_reader(data); reader.config_mut().trim_text(false); let mut buf = Vec::new(); let mut table = Table::default(); let mut table_column = TableColumn::default(); - let mut string_value = String::from(""); + let mut string_value = String::new(); loop { match reader.read_event_into(&mut buf) { Ok(Event::Empty(ref e)) => match e.name().into_inner() { b"tableColumn" => { table_column = TableColumn::default(); - for a in e.attributes().with_checks(false) { - match a { - Ok(ref attr) => match attr.key.0 { - b"name" => { - let attr_val = get_attribute_value(attr)?; - table_column.set_name(attr_val); - } - b"totalsRowLabel" => { - let attr_val = get_attribute_value(attr)?; - table_column.set_totals_row_label_str(&attr_val); - } - b"totalsRowFunction" => { - let attr_val = get_attribute_value(attr)?; - table_column.set_totals_row_function_str(&attr_val); - } - _ => {} - }, + for attr in e.attributes().with_checks(false).flatten() { + match attr.key.0 { + b"name" => { + let attr_val = get_attribute_value(&attr)?; + table_column.set_name(attr_val); + } + b"totalsRowLabel" => { + let attr_val = get_attribute_value(&attr)?; + table_column.set_totals_row_label_str(&attr_val); + } + b"totalsRowFunction" => { + let attr_val = get_attribute_value(&attr)?; + table_column.set_totals_row_function_str(&attr_val); + } _ => {} } } // add column to table (if it has a name) - if !table_column.get_name().is_empty() { + if !table_column.name().is_empty() { table.add_column(table_column); table_column = TableColumn::default(); } } b"tableStyleInfo" => { let mut name = String::new(); - let mut show_first_col = false; - let mut show_last_col = false; - let mut show_row_stripes = false; - let mut show_col_stripes = false; - for a in e.attributes().with_checks(false) { - match a { - Ok(ref attr) => { - let attr_val = get_attribute_value(attr)?; - match attr.key.0 { - b"name" => { - name = attr_val; - } - b"showFirstColumn" => { - show_first_col = attr_val == "1"; - } - b"showLastColumn" => { - show_last_col = attr_val == "1"; - } - b"showRowStripes" => { - show_row_stripes = attr_val == "1"; - } - b"showColumnStripes" => { - show_col_stripes = attr_val == "1"; - } - _ => {} + let mut show_first_col = ShowColumn::Hide; + let mut show_last_col = ShowColumn::Hide; + let mut show_row_stripes = ShowStripes::Hide; + let mut show_col_stripes = ShowStripes::Hide; + for attr in e.attributes().with_checks(false).flatten() { + let attr_val = get_attribute_value(&attr)?; + match attr.key.0 { + b"name" => { + name = attr_val; + } + b"showFirstColumn" => { + if attr_val == "1" { + show_first_col = ShowColumn::Show; + } + } + b"showLastColumn" => { + if attr_val == "1" { + show_last_col = ShowColumn::Show; + } + } + b"showRowStripes" => { + if attr_val == "1" { + show_row_stripes = ShowStripes::Show; + } + } + b"showColumnStripes" => { + if attr_val == "1" { + show_col_stripes = ShowStripes::Show; } } _ => {} } } - if !name.is_empty() { + if name.is_empty() { table.set_style_info(Some(TableStyleInfo::new( &name, show_first_col, @@ -90,6 +96,14 @@ pub(crate) fn read( show_row_stripes, show_col_stripes, ))); + } else { + table.set_style_info(Some(TableStyleInfo::new( + &name, + ShowColumn::Hide, + ShowColumn::Hide, + ShowStripes::Hide, + ShowStripes::Hide, + ))); } } _ => (), @@ -97,55 +111,47 @@ pub(crate) fn read( Ok(Event::Text(e)) => string_value = e.unescape().unwrap().to_string(), Ok(Event::Start(ref e)) => match e.name().into_inner() { b"table" => { - for a in e.attributes().with_checks(false) { - match a { - Ok(ref attr) => { - let attr_val = get_attribute_value(attr)?; - match attr.key.0 { - b"displayName" => { - table.set_display_name(&attr_val); - } - b"name" => { - table.set_name(&attr_val); - } - b"ref" => { - let area_coords: Vec<&str> = attr_val.split(':').collect(); - if area_coords.len() == 2 { - table.set_area((area_coords[0], area_coords[1])); - } - } - b"totalsRowShown" => { - table.set_totals_row_shown_str(&attr_val); - } - b"totalsRowCount" => { - table.set_totals_row_count_str(&attr_val); - } - _ => {} + for attr in e.attributes().with_checks(false).flatten() { + let attr_val = get_attribute_value(&attr)?; + match attr.key.0 { + b"displayName" => { + table.set_display_name(&attr_val); + } + b"name" => { + table.set_name(&attr_val); + } + b"ref" => { + let area_coords: Vec<&str> = attr_val.split(':').collect(); + if area_coords.len() == 2 { + table.set_area((area_coords[0], area_coords[1])); } } + b"totalsRowShown" => { + table.set_totals_row_shown_str(&attr_val); + } + b"totalsRowCount" => { + table.set_totals_row_count_str(&attr_val); + } _ => {} } } } b"tableColumn" => { table_column = TableColumn::default(); - for a in e.attributes().with_checks(false) { - match a { - Ok(ref attr) => match attr.key.0 { - b"name" => { - let attr_val = get_attribute_value(attr)?; - table_column.set_name(attr_val); - } - b"totalsRowLabel" => { - let attr_val = get_attribute_value(attr)?; - table_column.set_totals_row_label_str(&attr_val); - } - b"totalsRowFunction" => { - let attr_val = get_attribute_value(attr)?; - table_column.set_totals_row_function_str(&attr_val); - } - _ => {} - }, + for attr in e.attributes().with_checks(false).flatten() { + match attr.key.0 { + b"name" => { + let attr_val = get_attribute_value(&attr)?; + table_column.set_name(attr_val); + } + b"totalsRowLabel" => { + let attr_val = get_attribute_value(&attr)?; + table_column.set_totals_row_label_str(&attr_val); + } + b"totalsRowFunction" => { + let attr_val = get_attribute_value(&attr)?; + table_column.set_totals_row_function_str(&attr_val); + } _ => {} } } @@ -155,11 +161,11 @@ pub(crate) fn read( Ok(Event::End(ref e)) => match e.name().into_inner() { b"calculatedColumnFormula" => { table_column.set_calculated_column_formula(string_value); - string_value = String::from(""); + string_value = String::new(); } b"tableColumn" => { // add column to table (if it has a name) - if !table_column.get_name().is_empty() { + if !table_column.name().is_empty() { table.add_column(table_column); table_column = TableColumn::default(); } diff --git a/src/reader/xlsx/theme.rs b/src/reader/xlsx/theme.rs index 0fd302ff..08b14d07 100644 --- a/src/reader/xlsx/theme.rs +++ b/src/reader/xlsx/theme.rs @@ -1,15 +1,21 @@ +use std::io; + +use quick_xml::{ + Reader, + events::Event, +}; + use super::XlsxError; -use crate::structs::drawing::Theme; -use crate::xml_read_loop; -use quick_xml::events::Event; -use quick_xml::Reader; -use std::{io, result}; +use crate::{ + structs::drawing::Theme, + xml_read_loop, +}; pub fn read( arv: &mut zip::ZipArchive, target: &str, -) -> result::Result { - let r = io::BufReader::new(arv.by_name(&format!("xl/{}", target))?); +) -> Result { + let r = io::BufReader::new(arv.by_name(&format!("xl/{target}"))?); let mut reader = Reader::from_reader(r); reader.config_mut().trim_text(true); diff --git a/src/reader/xlsx/threaded_comment.rs b/src/reader/xlsx/threaded_comment.rs new file mode 100644 index 00000000..ff2055db --- /dev/null +++ b/src/reader/xlsx/threaded_comment.rs @@ -0,0 +1,31 @@ +use quick_xml::{ + Reader, + events::Event, +}; + +use crate::{ + office2019::threaded_comment::ThreadedComment, + structs::{ + Worksheet, + raw::RawFile, + }, + xml_read_loop, +}; + +pub(crate) fn read(worksheet: &mut Worksheet, drawing_file: &RawFile) { + let data = std::io::Cursor::new(drawing_file.file_data()); + let mut reader = Reader::from_reader(data); + reader.config_mut().trim_text(false); + + xml_read_loop!( + reader, + Event::Start(ref e) => { + if e.name().into_inner() == b"threadedComment" { + let mut obj = ThreadedComment::default(); + obj.set_attributes(&mut reader, e); + worksheet.add_threaded_comments(obj); + } + }, + Event::Eof => break, + ); +} diff --git a/src/reader/xlsx/vba_project_bin.rs b/src/reader/xlsx/vba_project_bin.rs index b0b3fd5d..f8d1f257 100644 --- a/src/reader/xlsx/vba_project_bin.rs +++ b/src/reader/xlsx/vba_project_bin.rs @@ -1,14 +1,18 @@ -use super::XlsxError; -use std::io::Read; -use std::{io, result}; +use std::{ + io, + io::Read, +}; -use crate::helper::const_str::*; -use crate::structs::Spreadsheet; +use super::XlsxError; +use crate::{ + helper::const_str::PKG_VBA_PROJECT, + structs::Workbook, +}; -pub(crate) fn read( +pub(crate) fn read( arv: &mut zip::ZipArchive, - spreadsheet: &mut Spreadsheet, -) -> result::Result<(), XlsxError> { + wb: &mut Workbook, +) -> Result<(), XlsxError> { let mut r = io::BufReader::new(match arv.by_name(PKG_VBA_PROJECT) { Ok(v) => v, Err(zip::result::ZipError::FileNotFound) => { @@ -21,7 +25,7 @@ pub(crate) fn read( let mut buf = Vec::new(); r.read_to_end(&mut buf)?; - spreadsheet.set_macros_code(buf); + wb.set_macros_code(buf); Ok(()) } diff --git a/src/reader/xlsx/vml_drawing.rs b/src/reader/xlsx/vml_drawing.rs index 3d2a2875..07fd4125 100644 --- a/src/reader/xlsx/vml_drawing.rs +++ b/src/reader/xlsx/vml_drawing.rs @@ -1,19 +1,26 @@ -use super::XlsxError; -use crate::structs::raw::RawFile; -use crate::structs::raw::RawRelationships; -use crate::structs::vml::Shape; -use crate::structs::Worksheet; -use crate::xml_read_loop; -use quick_xml::events::Event; -use quick_xml::Reader; -use std::result; +use quick_xml::{ + Reader, + events::Event, +}; + +use crate::{ + structs::{ + Worksheet, + raw::{ + RawFile, + RawRelationships, + }, + vml::Shape, + }, + xml_read_loop, +}; pub(crate) fn read( worksheet: &mut Worksheet, drawing_file: &RawFile, drawing_relationships: Option<&RawRelationships>, -) -> result::Result<(), XlsxError> { - let data = std::io::Cursor::new(drawing_file.get_file_data()); +) { + let data = std::io::Cursor::new(drawing_file.file_data()); let mut reader = Reader::from_reader(data); reader.config_mut().trim_text(true); @@ -26,32 +33,27 @@ pub(crate) fn read( if e.name().into_inner() == b"v:shape" { let mut obj = Shape::default(); obj.set_attributes(&mut reader, e, drawing_relationships); - match obj.get_client_data().get_comment_column_target() { - Some(_) => { - worksheet - .get_comments_mut() - .get_mut(comment_index) - .map(|comment| comment.set_shape(obj)); - comment_index += 1; - } - None => { - worksheet - .get_ole_objects_mut() - .get_ole_object_mut() - .get_mut(ole_index) - .map(|ole_obj| ole_obj.set_shape(obj)); - ole_index += 1; - } + if obj.client_data().comment_column_target().is_some() { + worksheet + .comments_mut() + .get_mut(comment_index) + .map(|comment| comment.set_shape(obj)); + comment_index += 1; + } else { + worksheet + .ole_objects_mut() + .ole_object_mut() + .get_mut(ole_index) + .map(|ole_obj| ole_obj.set_shape(obj)); + ole_index += 1; } } }, Event::Eof => break, ); - - Ok(()) } -//fn set_style(comment:&mut Comment, style_string:&str) { +// fn set_style(comment:&mut Comment, style_string:&str) { // let styles: Vec<&str> = style_string.split(';').collect(); // for style in &styles { // let params: Vec<&str> = style.split(':').collect(); @@ -64,8 +66,8 @@ pub(crate) fn read( // "margin-top" => comment.set_margin_top(value), // "width" => comment.set_width(value), // "height" => comment.set_height(value), -// "visibility" => comment.set_visible(if value == "visible" { true } else { false }), -// _ => {} +// "visibility" => comment.set_visible(if value == "visible" { +// true } else { false }), _ => {} // } // } // } diff --git a/src/reader/xlsx/workbook.rs b/src/reader/xlsx/workbook.rs index c6f5990c..49e90d2d 100644 --- a/src/reader/xlsx/workbook.rs +++ b/src/reader/xlsx/workbook.rs @@ -1,26 +1,34 @@ -use crate::xml_read_loop; +use std::io; -use super::driver::*; -use super::XlsxError; -use quick_xml::escape; -use quick_xml::events::Event; -use quick_xml::Reader; -use std::{io, result}; +use quick_xml::{ + Reader, + escape, + events::Event, +}; -use crate::helper::const_str::*; -use crate::structs::DefinedName; -use crate::structs::Spreadsheet; -use crate::structs::WorkbookProtection; -use crate::structs::WorkbookView; -use crate::structs::Worksheet; +use super::{ + XlsxError, + driver::get_attribute, +}; +use crate::{ + helper::const_str::PKG_WORKBOOK, + structs::{ + DefinedName, + Workbook, + WorkbookProtection, + WorkbookView, + Worksheet, + }, + xml_read_loop, +}; pub(crate) fn read( arv: &mut zip::read::ZipArchive, -) -> result::Result { +) -> Result { let r = io::BufReader::new(arv.by_name(PKG_WORKBOOK)?); let mut reader = Reader::from_reader(r); reader.config_mut().trim_text(true); - let mut spreadsheet = Spreadsheet::default(); + let mut wb = Workbook::default(); let mut defined_names: Vec = Vec::new(); @@ -31,12 +39,12 @@ pub(crate) fn read( b"workbookView" => { let mut obj = WorkbookView::default(); obj.set_attributes(&mut reader, e); - spreadsheet.set_workbook_view(obj); + wb.set_workbook_view(obj); } b"workbookProtection" => { let mut obj = WorkbookProtection::default(); obj.set_attributes(&mut reader, e); - spreadsheet.set_workbook_protection(obj); + wb.set_workbook_protection(obj); } b"sheet" => { let name_value = get_attribute(e, b"name").unwrap(); @@ -50,12 +58,12 @@ pub(crate) fn read( if let Some(v) = state { worksheet.set_state_str(&v); } - spreadsheet.add_sheet(worksheet); + wb.add_sheet(worksheet).unwrap(); } b"pivotCache" => { let cache_id = get_attribute(e, b"cacheId").unwrap(); let r_id = get_attribute(e, b"r:id").unwrap(); - spreadsheet.add_pivot_caches((r_id, cache_id, String::from(""))); + wb.add_pivot_caches((r_id, cache_id, String::new())); } _ => (), } @@ -72,25 +80,20 @@ pub(crate) fn read( for defined_name in &defined_names { if defined_name.has_local_sheet_id() { - let local_sheet_id = defined_name.get_local_sheet_id().clone() as usize; - spreadsheet - .get_sheet_mut(&local_sheet_id) + let local_sheet_id = defined_name.local_sheet_id() as usize; + wb.sheet_mut(local_sheet_id) .unwrap() .add_defined_names(defined_name.clone()); } else { - match defined_name.get_address_obj().get(0) { - Some(v) => match spreadsheet.get_sheet_by_name_mut(v.get_sheet_name()) { - Some(s) => { - s.add_defined_names(defined_name.clone()); - continue; - } - None => {} - }, - None => {} + if let Some(v) = defined_name.address_obj().first() { + if let Ok(s) = wb.sheet_by_name_mut(v.sheet_name()) { + s.add_defined_names(defined_name.clone()); + continue; + } } - spreadsheet.add_defined_names(defined_name.clone()); + wb.add_defined_names(defined_name.clone()); } } - Ok(spreadsheet) + Ok(wb) } diff --git a/src/reader/xlsx/workbook_rels.rs b/src/reader/xlsx/workbook_rels.rs index d1210582..a6c96334 100644 --- a/src/reader/xlsx/workbook_rels.rs +++ b/src/reader/xlsx/workbook_rels.rs @@ -1,15 +1,29 @@ -use super::driver::*; -use super::XlsxError; -use crate::helper::const_str::*; -use crate::structs::Spreadsheet; -use quick_xml::events::Event; -use quick_xml::Reader; -use std::{io, result}; +use std::io; + +use quick_xml::{ + Reader, + events::Event, +}; + +use super::{ + XlsxError, + driver::{ + get_attribute, + xml_read_loop, + }, +}; +use crate::{ + helper::const_str::{ + PIVOT_CACHE_DEF_NS, + PKG_WORKBOOK_RELS, + }, + structs::Workbook, +}; pub(crate) fn read( arv: &mut zip::read::ZipArchive, - spreadsheet: &mut Spreadsheet, -) -> result::Result, XlsxError> { + wb: &mut Workbook, +) -> Result, XlsxError> { let r = io::BufReader::new(arv.by_name(PKG_WORKBOOK_RELS)?); let mut reader = Reader::from_reader(r); reader.config_mut().trim_text(true); @@ -25,13 +39,12 @@ pub(crate) fn read( let target_value = get_attribute(e, b"Target").unwrap(); let target_value = target_value .strip_prefix("/xl/") - .map(|t| t.to_owned()) + .map(ToOwned::to_owned) .unwrap_or(target_value); if type_value == PIVOT_CACHE_DEF_NS { - spreadsheet.update_pivot_caches(id_value, target_value); - } else { - result.push((id_value, type_value, target_value)); + wb.update_pivot_caches(id_value.as_str(), target_value.as_str()); } + result.push((id_value, type_value, target_value)); } }, Event::Eof => break, diff --git a/src/reader/xlsx/worksheet.rs b/src/reader/xlsx/worksheet.rs index 4acceb18..3c995f02 100644 --- a/src/reader/xlsx/worksheet.rs +++ b/src/reader/xlsx/worksheet.rs @@ -1,24 +1,39 @@ -use super::driver::*; -use super::XlsxError; -use quick_xml::events::Event; -use quick_xml::Reader; use std::collections::HashMap; -use crate::helper::formula::*; -use crate::structs::office2010::excel::DataValidations as DataValidations2010; -use crate::structs::raw::RawRelationships; -use crate::structs::raw::RawWorksheet; -use crate::structs::Cells; -use crate::structs::Columns; -use crate::structs::ConditionalFormatting; -use crate::structs::DataValidations; -use crate::structs::Hyperlink; -use crate::structs::OleObjects; -use crate::structs::Row; -use crate::structs::SharedStringTable; -use crate::structs::SheetProtection; -use crate::structs::Stylesheet; -use crate::structs::Worksheet; +use quick_xml::{ + Reader, + events::Event, +}; + +use super::{ + XlsxError, + driver::{ + get_attribute, + get_attribute_value, + xml_read_loop, + }, +}; +use crate::{ + helper::formula::FormulaToken, + structs::{ + Cells, + Columns, + ConditionalFormatting, + DataValidations, + Hyperlink, + OleObjects, + Row, + SharedStringTable, + SheetProtection, + Stylesheet, + Worksheet, + office2010::excel::DataValidations as DataValidations2010, + raw::{ + RawRelationships, + RawWorksheet, + }, + }, +}; pub(crate) fn read( worksheet: &mut Worksheet, @@ -26,7 +41,7 @@ pub(crate) fn read( shared_string_table: &SharedStringTable, stylesheet: &Stylesheet, ) -> Result<(), XlsxError> { - let data = std::io::Cursor::new(raw_data_of_worksheet.get_worksheet_file().get_file_data()); + let data = std::io::Cursor::new(raw_data_of_worksheet.worksheet_file().file_data()); let mut reader = Reader::from_reader(data); reader.config_mut().trim_text(true); let mut formula_shared_list: HashMap)> = HashMap::new(); @@ -39,19 +54,18 @@ pub(crate) fn read( Ok(ref attr) if attr.key.0 == b"codeName" => { worksheet.set_code_name(get_attribute_value(attr)?); } - Ok(_) => {} - Err(_) => {} + Ok(_) | Err(_) => {} } } } b"sheetViews" => { worksheet - .get_sheet_views_mut() + .sheet_views_mut() .set_attributes(&mut reader, e); } b"sheetFormatPr" => { worksheet - .get_sheet_format_properties_mut() + .sheet_format_properties_mut() .set_attributes(&mut reader, e); } b"selection" => { @@ -60,8 +74,7 @@ pub(crate) fn read( Ok(ref attr) if attr.key.0 == b"activeCell" => { worksheet.set_active_cell(get_attribute_value(attr)?); } - Ok(_) => {} - Err(_) => {} + Ok(_) | Err(_) => {} } } } @@ -70,7 +83,7 @@ pub(crate) fn read( obj.set_attributes( &mut reader, e, - worksheet.get_cell_collection_crate_mut(), + worksheet.cells_crate_mut(), shared_string_table, stylesheet, &mut formula_shared_list, @@ -88,12 +101,12 @@ pub(crate) fn read( } b"mergeCells" => { worksheet - .get_merge_cells_crate_mut() + .merge_cells_crate_mut() .set_attributes(&mut reader, e); } b"conditionalFormatting" => { let mut obj = ConditionalFormatting::default(); - obj.set_attributes(&mut reader, e, stylesheet.get_differential_formats()); + obj.set_attributes(&mut reader, e, stylesheet.differential_formats()); worksheet.add_conditional_formatting_collection(obj); } b"dataValidations" => { @@ -111,23 +124,23 @@ pub(crate) fn read( obj.set_attributes( &mut reader, e, - raw_data_of_worksheet.get_worksheet_relationships().unwrap(), + raw_data_of_worksheet.worksheet_relationships().unwrap(), ); worksheet.set_ole_objects(obj); } b"headerFooter" => { worksheet - .get_header_footer_mut() + .header_footer_mut() .set_attributes(&mut reader, e); } b"rowBreaks" => { worksheet - .get_row_breaks_mut() + .row_breaks_mut() .set_attributes(&mut reader, e); } b"colBreaks" => { worksheet - .get_column_breaks_mut() + .column_breaks_mut() .set_attributes(&mut reader, e); } _ => (), @@ -139,19 +152,18 @@ pub(crate) fn read( Ok(ref attr) if attr.key.0 == b"codeName" => { worksheet.set_code_name(get_attribute_value(attr)?); } - Ok(_) => {} - Err(_) => {} + Ok(_) | Err(_) => {} } } } b"tabColor" => { worksheet - .get_tab_color_mut() + .tab_color_mut() .set_attributes(&mut reader, e, true); } b"sheetFormatPr" => { worksheet - .get_sheet_format_properties_mut() + .sheet_format_properties_mut() .set_attributes(&mut reader, e); } b"selection" => { @@ -160,8 +172,7 @@ pub(crate) fn read( Ok(ref attr) if attr.key.0 == b"activeCell" => { worksheet.set_active_cell(get_attribute_value(attr)?); } - Ok(_) => {} - Err(_) => {} + Ok(_) | Err(_) => {} } } } @@ -170,7 +181,7 @@ pub(crate) fn read( obj.set_attributes( &mut reader, e, - worksheet.get_cell_collection_crate_mut(), + worksheet.cells_crate_mut(), shared_string_table, stylesheet, &mut formula_shared_list, @@ -183,23 +194,26 @@ pub(crate) fn read( } b"pageMargins" => { worksheet - .get_page_margins_mut() + .page_margins_mut() .set_attributes(&mut reader, e); } b"hyperlink" => { - let (coor, hyperlink) = get_hyperlink(e, raw_data_of_worksheet.get_worksheet_relationships()); - worksheet.get_cell_mut(coor).set_hyperlink(hyperlink); + let (coor, hyperlink) = get_hyperlink( + e, + raw_data_of_worksheet.worksheet_relationships() + ); + worksheet.cell_mut(coor).set_hyperlink(hyperlink); } b"printOptions" => { worksheet - .get_print_options_mut() + .print_options_mut() .set_attributes(&mut reader, e); } b"pageSetup" => { - worksheet.get_page_setup_mut().set_attributes( + worksheet.page_setup_mut().set_attributes( &mut reader, e, - raw_data_of_worksheet.get_worksheet_relationships(), + raw_data_of_worksheet.worksheet_relationships(), ); } b"sheetProtection" => { @@ -222,8 +236,8 @@ pub(crate) fn read_lite( raw_data_of_worksheet: &RawWorksheet, shared_string_table: &SharedStringTable, stylesheet: &Stylesheet, -) -> Result { - let data = std::io::Cursor::new(raw_data_of_worksheet.get_worksheet_file().get_file_data()); +) -> Cells { + let data = std::io::Cursor::new(raw_data_of_worksheet.worksheet_file().file_data()); let mut reader = Reader::from_reader(data); reader.config_mut().trim_text(true); @@ -262,7 +276,7 @@ pub(crate) fn read_lite( Event::Eof => break, ); - Ok(cells) + cells } fn get_hyperlink( @@ -270,7 +284,6 @@ fn get_hyperlink( raw_relationships: Option<&RawRelationships>, ) -> (String, Hyperlink) { let mut hyperlink = Hyperlink::default(); - let mut rid = String::from(""); let coordition = get_attribute(e, b"ref").unwrap_or_default(); if let Some(v) = get_attribute(e, b"location") { @@ -278,8 +291,8 @@ fn get_hyperlink( hyperlink.set_location(true); } if let Some(v) = get_attribute(e, b"r:id") { - let relationship = raw_relationships.unwrap().get_relationship_by_rid(&v); - hyperlink.set_url(relationship.get_target()); + let relationship = raw_relationships.unwrap().relationship_by_rid(&v); + hyperlink.set_url(relationship.target()); } (coordition, hyperlink) } diff --git a/src/structs.rs b/src/structs.rs index 790e40cb..06369b41 100644 --- a/src/structs.rs +++ b/src/structs.rs @@ -1,500 +1,191 @@ -//! store structs. +/// A macro to simplify module usage and visibility. +/// +/// This macro takes a list of modules, makes them private, +/// and re-exports their contents as public in the current scope. +macro_rules! pub_mod_use { + ($($vis:vis $mod:ident),*) => { + $( + mod $mod; + $vis use self::$mod::*; + )* + }; +} pub mod custom_properties; pub mod drawing; pub mod office; pub mod office2010; +pub mod office2019; pub mod raw; pub mod vml; -mod error; -pub use self::error::*; - -mod spreadsheet; -pub use self::spreadsheet::*; - -mod worksheet; -pub use self::worksheet::*; - -mod properties; -pub use self::properties::*; - -mod cell; -pub use self::cell::*; - -mod cells; -pub use self::cells::*; - -mod hyperlink; -pub use self::hyperlink::*; - -mod color; -pub use self::color::*; - -mod page_setup; -pub use self::page_setup::*; - -mod page_margins; -pub use self::page_margins::*; - -mod header_footer; -pub use self::header_footer::*; - -mod sheet_view; -pub use self::sheet_view::*; - -mod auto_filter; -pub use self::auto_filter::*; - -mod column; -pub use self::column::*; - -mod style; -pub use self::style::*; - -mod font; -pub use self::font::*; - -mod fill; -pub use self::fill::*; - -mod borders; -pub(crate) use self::borders::*; - -mod border; -pub use self::border::*; - -mod alignment; -pub use self::alignment::*; - -mod conditional_formatting_rule; -pub use self::conditional_formatting_rule::*; - -mod protection; -pub use self::protection::*; - -mod rich_text; -pub use self::rich_text::*; - -mod text_element; -pub use self::text_element::*; - -mod cell_style; -pub use self::cell_style::*; - -mod defined_name; -pub use self::defined_name::*; - -mod comment; -pub use self::comment::*; - -mod coordinate; -pub use self::coordinate::*; - -mod range; -pub use self::range::*; - -mod conditional_formatting; -pub use self::conditional_formatting::*; - -mod address; -pub use self::address::*; - -mod anchor; -pub use self::anchor::*; - -mod boolean_value; -pub use self::boolean_value::*; - -mod u_int32_value; -pub use self::u_int32_value::*; - -mod u_int16_value; -pub use self::u_int16_value::*; - -mod int32_value; -pub use self::int32_value::*; - -mod int16_value; -pub use self::int16_value::*; - -mod string_value; -pub use self::string_value::*; - -mod double_value; -pub use self::double_value::*; - -mod enum_value; -pub use self::enum_value::*; - -mod enum_trait; -pub use self::enum_trait::*; - -mod byte_value; -pub use self::byte_value::*; - -mod s_byte_value; -pub use self::s_byte_value::*; - -mod int64_value; -pub use self::int64_value::*; - -mod row; -pub use self::row::*; - -mod font_name; -pub use self::font_name::*; - -mod font_size; -pub use self::font_size::*; - -mod font_family_numbering; -pub use self::font_family_numbering::*; - -mod bold; -pub use self::bold::*; - -mod italic; -pub use self::italic::*; - -mod underline_values; -pub use self::underline_values::*; - -mod underline; -pub use self::underline::*; - -mod strike; -pub use self::strike::*; - -mod font_char_set; -pub use self::font_char_set::*; - -mod font_scheme_values; -pub use self::font_scheme_values::*; - -mod font_scheme; -pub use self::font_scheme::*; - -mod fonts; -pub(crate) use self::fonts::*; - -mod pattern_fill; -pub use self::pattern_fill::*; - -mod pattern_values; -pub use self::pattern_values::*; - -mod fills; -pub(crate) use self::fills::*; - -mod numbering_format; -pub use self::numbering_format::*; - -mod numbering_formats; -pub(crate) use self::numbering_formats::*; - -mod stylesheet; -pub(crate) use self::stylesheet::*; - -mod border_properties_type; -pub use self::border_properties_type::*; - -mod border_style_values; -pub use self::border_style_values::*; - -mod borders_crate; -pub(crate) use self::borders_crate::*; - -mod cell_format; -pub(crate) use self::cell_format::*; - -mod horizontal_alignment_values; -pub use self::horizontal_alignment_values::*; - -mod vertical_alignment_values; -pub use self::vertical_alignment_values::*; - -mod cell_formats; -pub(crate) use self::cell_formats::*; - -mod cell_style_formats; -pub(crate) use self::cell_style_formats::*; - -mod cell_styles; -pub(crate) use self::cell_styles::*; - -mod differential_format; -pub(crate) use self::differential_format::*; - -mod differential_formats; -pub(crate) use self::differential_formats::*; - -mod mru_colors; -pub(crate) use self::mru_colors::*; - -mod colors; -pub(crate) use self::colors::*; - -mod shared_string_table; -pub(crate) use self::shared_string_table::*; - -mod shared_string_item; -pub(crate) use self::shared_string_item::*; - -mod text; -pub(crate) use self::text::*; - -mod phonetic_run; -pub(crate) use self::phonetic_run::*; - -mod gradient_fill; -pub use self::gradient_fill::*; - -mod gradient_stop; -pub use self::gradient_stop::*; - -mod vertical_alignment_run_values; -pub use self::vertical_alignment_run_values::*; - -mod vertical_text_alignment; -pub use self::vertical_text_alignment::*; - -mod cell_value; -pub use self::cell_value::*; - -mod row_reference; -pub use self::row_reference::*; - -mod column_reference; -pub use self::column_reference::*; - -mod columns; -pub(crate) use self::columns::*; - -mod sequence_of_references; -pub use self::sequence_of_references::*; - -mod selection; -pub use self::selection::*; - -mod pane_values; -pub use self::pane_values::*; - -mod pane; -pub use self::pane::*; - -mod pane_state_values; -pub use self::pane_state_values::*; - -mod workbook_view; -pub use self::workbook_view::*; - -mod ole_objects; -pub use self::ole_objects::*; - -mod ole_object; -pub use self::ole_object::*; - -mod embedded_object_properties; -pub use self::embedded_object_properties::*; - -mod object_anchor; -pub use self::object_anchor::*; - -mod from_marker; -pub use self::from_marker::*; - -mod to_marker; -pub use self::to_marker::*; - -mod true_false_value; -pub use self::true_false_value::*; - -mod true_false_blank_value; -pub use self::true_false_blank_value::*; - -mod image; -pub use self::image::*; - -mod chart; -pub use self::chart::*; - -mod chart_type; -pub use self::chart_type::*; - -mod merge_cells; -pub(crate) use self::merge_cells::*; - -mod print_options; -pub use self::print_options::*; - -mod orientation_values; -pub use self::orientation_values::*; - -mod odd_header; -pub use self::odd_header::*; - -mod odd_footer; -pub use self::odd_footer::*; - -mod r#break; -pub use self::r#break::*; - -mod row_breaks; -pub use self::row_breaks::*; - -mod column_breaks; -pub use self::column_breaks::*; - -mod sheet_view_values; -pub use self::sheet_view_values::*; - -mod writer_manager; -pub use self::writer_manager::*; - -mod sheet_views; -pub use self::sheet_views::*; - -mod rows; -pub(crate) use self::rows::*; - -mod media_object; -pub(crate) use self::media_object::*; - -mod csv_writer_option; -pub use self::csv_writer_option::*; - -mod csv_encode_values; -pub use self::csv_encode_values::*; - -mod cell_raw_value; -pub use self::cell_raw_value::*; - -mod conditional_format_values; -pub use self::conditional_format_values::*; - -mod conditional_formatting_operator_values; -pub use self::conditional_formatting_operator_values::*; - -mod time_period_values; -pub use self::time_period_values::*; - -mod color_scale; -pub use self::color_scale::*; - -mod conditional_format_value_object; -pub use self::conditional_format_value_object::*; - -mod conditional_format_value_object_values; -pub use self::conditional_format_value_object_values::*; - -mod data_bar; -pub use self::data_bar::*; - -mod icon_set; -pub use self::icon_set::*; - -mod formula; -pub use self::formula::*; - -mod table; -pub use self::table::*; - -mod data_validation_values; -pub use self::data_validation_values::*; - -mod data_validation_operator_values; -pub use self::data_validation_operator_values::*; - -mod data_validation; -pub use self::data_validation::*; - -mod data_validations; -pub use self::data_validations::*; - -mod sheet_format_properties; -pub use self::sheet_format_properties::*; - -mod sheet_protection; -pub use self::sheet_protection::*; - -mod workbook_protection; -pub use self::workbook_protection::*; - -mod cell_formula; -pub use self::cell_formula::*; - -mod cell_formula_values; -pub use self::cell_formula_values::*; - -mod totals_row_function_values; -pub use self::totals_row_function_values::*; - -mod sheet_state_values; -pub use self::sheet_state_values::*; - -mod pivot_table_definition; -pub use self::pivot_table_definition::*; - -mod pivot_table; -pub use self::pivot_table::*; - -mod pivot_cache_definition; -pub use self::pivot_cache_definition::*; - -mod location; -pub use self::location::*; - -mod pivot_fields; -pub use self::pivot_fields::*; - -mod pivot_field; -pub use self::pivot_field::*; - -mod row_items; -pub use self::row_items::*; - -mod row_item; -pub use self::row_item::*; - -mod column_fields; -pub use self::column_fields::*; - -mod field; -pub use self::field::*; - -mod column_items; -pub use self::column_items::*; - -mod item_values; -pub use self::item_values::*; - -mod member_property_index; -pub use self::member_property_index::*; - -mod data_fields; -pub use self::data_fields::*; - -mod data_field; -pub use self::data_field::*; - -mod pivot_table_style; -pub use self::pivot_table_style::*; - -mod cache_source; -pub use self::cache_source::*; - -mod source_values; -pub use self::source_values::*; - -mod worksheet_source; -pub use self::worksheet_source::*; - -mod cache_fields; -pub use self::cache_fields::*; - -mod cache_field; -pub use self::cache_field::*; - -mod shared_items; -pub use self::shared_items::*; +pub_mod_use![ + pub(crate) borders_crate, + pub(crate) borders, + pub(crate) cell_format, + pub(crate) cell_formats, + pub(crate) cell_style_formats, + pub(crate) cell_styles, + pub(crate) colors, + pub(crate) columns, + pub(crate) differential_format, + pub(crate) differential_formats, + pub(crate) fills, + pub(crate) fonts, + pub(crate) media_object, + pub(crate) merge_cells, + pub(crate) mru_colors, + pub(crate) numbering_formats, + pub(crate) phonetic_run, + pub(crate) rows, + pub(crate) shared_string_item, + pub(crate) shared_string_table, + pub(crate) stylesheet, + pub(crate) text, + + pub address, + pub attributes, + pub alignment, + pub anchor, + pub auto_filter, + pub bold, + pub boolean_value, + pub border_properties_type, + pub border_style_values, + pub border, + pub byte_value, + pub cache_field, + pub cache_fields, + pub cache_source, + pub cell_formula_values, + pub cell_formula, + pub cell_raw_value, + pub cell_style, + pub cell_value, + pub cell, + pub cells, + pub chart_type, + pub chart, + pub color_scale, + pub color, + pub column_breaks, + pub column_fields, + pub column_items, + pub column_reference, + pub column, + pub comment, + pub comment_text, + pub conditional_format_value_object_values, + pub conditional_format_value_object, + pub conditional_format_values, + pub conditional_formatting_operator_values, + pub conditional_formatting_rule, + pub conditional_formatting, + pub coordinate, + pub csv_encode_values, + pub csv_writer_option, + pub data_bar, + pub data_field, + pub data_fields, + pub date_time_value, + pub data_validation_operator_values, + pub data_validation_values, + pub data_validation, + pub data_validations, + pub defined_name, + pub double_value, + pub embedded_object_properties, + pub enum_trait, + pub enum_value, + pub error, + pub field, + pub fill, + pub font_char_set, + pub font_family_numbering, + pub font_name, + pub font_scheme_values, + pub font_scheme, + pub font_size, + pub font, + pub formula, + pub from_marker, + pub gradient_fill, + pub gradient_stop, + pub header_footer, + pub horizontal_alignment_values, + pub hyperlink, + pub icon_set, + pub image, + pub int16_value, + pub int32_value, + pub int64_value, + pub italic, + pub item_values, + pub location, + pub member_property_index, + pub numbering_format, + pub object_anchor, + pub odd_footer, + pub odd_header, + pub ole_object, + pub ole_objects, + pub orientation_values, + pub page_margins, + pub page_setup, + pub pane_state_values, + pub pane_values, + pub pane, + pub pattern_fill, + pub pattern_values, + pub pivot_cache_definition, + pub pivot_field, + pub pivot_fields, + pub pivot_table_definition, + pub pivot_table_style, + pub pivot_table, + pub print_options, + pub properties, + pub protection, + pub r#break, + pub range, + pub rich_text, + pub row_breaks, + pub row_item, + pub row_items, + pub row_reference, + pub row, + pub s_byte_value, + pub selection, + pub sequence_of_references, + pub shared_items, + pub sheet_format_properties, + pub sheet_protection, + pub sheet_state_values, + pub sheet_view_values, + pub sheet_view, + pub sheet_views, + pub source_values, + pub strike, + pub string_value, + pub style, + pub table, + pub text_element, + pub time_period_values, + pub to_marker, + pub totals_row_function_values, + pub true_false_blank_value, + pub true_false_value, + pub u_int16_value, + pub u_int32_value, + pub underline_values, + pub underline, + pub vertical_alignment_run_values, + pub vertical_alignment_values, + pub vertical_text_alignment, + pub workbook_protection, + pub workbook_view, + pub workbook, + pub worksheet_source, + pub worksheet, + pub writer_manager +]; diff --git a/src/structs/address.rs b/src/structs/address.rs index 10a8bb66..770e9776 100644 --- a/src/structs/address.rs +++ b/src/structs/address.rs @@ -1,22 +1,36 @@ use super::Range; -use crate::helper::address::*; -use crate::helper::coordinate::*; -use crate::traits::AdjustmentCoordinate; -use crate::traits::AdjustmentCoordinateWithSheet; -use fancy_regex::Regex; +use crate::{ + helper::{ + address::split_address, + coordinate::index_from_coordinate, + utils::compile_regex, + }, + traits::{ + AdjustmentCoordinate, + AdjustmentCoordinateWithSheet, + }, +}; #[derive(Clone, Default, Debug)] pub struct Address { sheet_name: Box, - range: Range, + range: Range, } impl Address { #[inline] - pub fn get_sheet_name(&self) -> &str { + #[must_use] + pub fn sheet_name(&self) -> &str { &self.sheet_name } + #[inline] + #[must_use] + #[deprecated(since = "3.0.0", note = "Use sheet_name()")] + pub fn get_sheet_name(&self) -> &str { + self.sheet_name() + } + #[inline] pub fn set_sheet_name>(&mut self, value: S) -> &mut Self { self.sheet_name = value.into().into_boxed_str(); @@ -24,15 +38,29 @@ impl Address { } #[inline] - pub fn get_range(&self) -> &Range { + #[must_use] + pub fn range(&self) -> &Range { &self.range } #[inline] - pub fn get_range_mut(&mut self) -> &mut Range { + #[must_use] + #[deprecated(since = "3.0.0", note = "Use range()")] + pub fn get_range(&self) -> &Range { + self.range() + } + + #[inline] + pub fn range_mut(&mut self) -> &mut Range { &mut self.range } + #[inline] + #[deprecated(since = "3.0.0", note = "Use range_mut()")] + pub fn get_range_mut(&mut self) -> &mut Range { + self.range_mut() + } + #[inline] pub fn set_range(&mut self, value: Range) -> &mut Self { self.range = value; @@ -43,24 +71,38 @@ impl Address { let org_value = value.into(); let (sheet_name, range) = split_address(&org_value); self.range.set_range(range); - if sheet_name != "" { + if !sheet_name.is_empty() { self.sheet_name = sheet_name.into(); } self } #[inline] + #[must_use] + pub fn address(&self) -> String { + self.address_crate(false) + } + + #[inline] + #[must_use] + #[deprecated(since = "3.0.0", note = "Use address()")] pub fn get_address(&self) -> String { - self.get_address_crate(false) + self.address() + } + + #[inline] + pub(crate) fn address_ptn2(&self) -> String { + self.address_crate(true) } #[inline] + #[deprecated(since = "3.0.0", note = "Use address_ptn2()")] pub(crate) fn get_address_ptn2(&self) -> String { - self.get_address_crate(true) + self.address_ptn2() } - pub(crate) fn get_address_crate(&self, is_ptn2: bool) -> String { - let range = self.range.get_range(); + pub(crate) fn address_crate(&self, is_ptn2: bool) -> String { + let range = self.range.range(); if self.sheet_name.is_empty() { return range; } @@ -70,28 +112,27 @@ impl Address { with_space_char = "'"; } if is_ptn2 { - if sheet_name.contains("!") { + if sheet_name.contains('!') { with_space_char = "'"; } - if sheet_name.contains("'") { + if sheet_name.contains('\'') { with_space_char = "'"; - sheet_name = sheet_name.replace("'", "''").into_boxed_str(); + sheet_name = sheet_name.replace('\'', "''").into_boxed_str(); } - if sheet_name.contains(r#"""#) { + if sheet_name.contains('"') { with_space_char = "'"; } - if with_space_char == "" { - lazy_static! { - static ref RE: Regex = Regex::new(r"[^0-9a-zA-Z]").unwrap(); - } - if RE.is_match(&sheet_name).unwrap_or(false) { - with_space_char = "'"; - } + if with_space_char.is_empty() + && compile_regex!(r"[^0-9a-zA-Z]") + .is_match(&sheet_name) + .unwrap_or(false) + { + with_space_char = "'"; } - if with_space_char == "" { - if (None, None, None, None) != index_from_coordinate(&sheet_name) { - with_space_char = "'"; - } + if with_space_char.is_empty() + && (None, None, None, None) != index_from_coordinate(&sheet_name) + { + with_space_char = "'"; } } format!( @@ -99,16 +140,21 @@ impl Address { &with_space_char, sheet_name, &with_space_char, range ) } + + #[deprecated(since = "3.0.0", note = "Use address_crate()")] + pub(crate) fn get_address_crate(&self, is_ptn2: bool) -> String { + self.address_crate(is_ptn2) + } } impl AdjustmentCoordinateWithSheet for Address { #[inline] fn adjustment_insert_coordinate_with_sheet( &mut self, sheet_name: &str, - root_col_num: &u32, - offset_col_num: &u32, - root_row_num: &u32, - offset_row_num: &u32, + root_col_num: u32, + offset_col_num: u32, + root_row_num: u32, + offset_row_num: u32, ) { if &*self.sheet_name == sheet_name { self.range.adjustment_insert_coordinate( @@ -124,10 +170,10 @@ impl AdjustmentCoordinateWithSheet for Address { fn adjustment_remove_coordinate_with_sheet( &mut self, sheet_name: &str, - root_col_num: &u32, - offset_col_num: &u32, - root_row_num: &u32, - offset_row_num: &u32, + root_col_num: u32, + offset_col_num: u32, + root_row_num: u32, + offset_row_num: u32, ) { if &*self.sheet_name == sheet_name { self.range.adjustment_remove_coordinate( @@ -143,10 +189,10 @@ impl AdjustmentCoordinateWithSheet for Address { fn is_remove_coordinate_with_sheet( &self, sheet_name: &str, - root_col_num: &u32, - offset_col_num: &u32, - root_row_num: &u32, - offset_row_num: &u32, + root_col_num: u32, + offset_col_num: u32, + root_row_num: u32, + offset_row_num: u32, ) -> bool { &*self.sheet_name == sheet_name && self.range.is_remove_coordinate( diff --git a/src/structs/alignment.rs b/src/structs/alignment.rs index 383bd186..a886e59b 100644 --- a/src/structs/alignment.rs +++ b/src/structs/alignment.rs @@ -1,29 +1,48 @@ // alignment -use super::BooleanValue; -use super::EnumValue; -use super::HorizontalAlignmentValues; -use super::UInt32Value; -use super::VerticalAlignmentValues; -use crate::reader::driver::*; -use crate::writer::driver::*; -use md5::Digest; -use quick_xml::events::BytesStart; -use quick_xml::Reader; -use quick_xml::Writer; use std::io::Cursor; +use md5::Digest; +use quick_xml::{ + Reader, + Writer, + events::BytesStart, +}; + +use super::{ + BooleanValue, + EnumValue, + HorizontalAlignmentValues, + UInt32Value, + VerticalAlignmentValues, +}; +use crate::{ + reader::driver::{ + get_attribute, + set_string_from_xml, + }, + writer::driver::write_start_tag, +}; + #[derive(Default, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] pub struct Alignment { - horizontal: EnumValue, - vertical: EnumValue, - wrap_text: BooleanValue, + horizontal: EnumValue, + vertical: EnumValue, + wrap_text: BooleanValue, text_rotation: UInt32Value, } impl Alignment { #[inline] + #[must_use] + pub fn horizontal(&self) -> &HorizontalAlignmentValues { + self.horizontal.value() + } + + #[inline] + #[must_use] + #[deprecated(since = "3.0.0", note = "Use horizontal()")] pub fn get_horizontal(&self) -> &HorizontalAlignmentValues { - self.horizontal.get_value() + self.horizontal() } #[inline] @@ -32,8 +51,16 @@ impl Alignment { } #[inline] + #[must_use] + pub fn vertical(&self) -> &VerticalAlignmentValues { + self.vertical.value() + } + + #[inline] + #[must_use] + #[deprecated(since = "3.0.0", note = "Use vertical()")] pub fn get_vertical(&self) -> &VerticalAlignmentValues { - self.vertical.get_value() + self.vertical() } #[inline] @@ -42,8 +69,16 @@ impl Alignment { } #[inline] - pub fn get_wrap_text(&self) -> &bool { - self.wrap_text.get_value() + #[must_use] + pub fn wrap_text(&self) -> bool { + self.wrap_text.value() + } + + #[inline] + #[must_use] + #[deprecated(since = "3.0.0", note = "Use wrap_text()")] + pub fn get_wrap_text(&self) -> bool { + self.wrap_text() } #[inline] @@ -52,8 +87,16 @@ impl Alignment { } #[inline] - pub fn get_text_rotation(&self) -> &u32 { - self.text_rotation.get_value() + #[must_use] + pub fn text_rotation(&self) -> u32 { + self.text_rotation.value() + } + + #[inline] + #[must_use] + #[deprecated(since = "3.0.0", note = "Use text_rotation()")] + pub fn get_text_rotation(&self) -> u32 { + self.text_rotation() } #[inline] @@ -61,19 +104,24 @@ impl Alignment { self.text_rotation.set_value(value); } - pub(crate) fn get_hash_code(&self) -> String { + pub(crate) fn hash_code(&self) -> String { format!( "{:x}", md5::Md5::digest(format!( "{}{}{}{}", - &self.horizontal.get_hash_string(), - &self.vertical.get_hash_string(), - &self.wrap_text.get_hash_string(), - &self.text_rotation.get_hash_string(), + &self.horizontal.hash_string(), + &self.vertical.hash_string(), + &self.wrap_text.hash_string(), + &self.text_rotation.hash_string(), )) ) } + #[deprecated(since = "3.0.0", note = "Use hash_code()")] + pub(crate) fn get_hash_code(&self) -> String { + self.hash_code() + } + #[inline] pub(crate) fn set_attributes( &mut self, @@ -88,19 +136,19 @@ impl Alignment { pub(crate) fn write_to(&self, writer: &mut Writer>>) { // alignment - let mut attributes: Vec<(&str, &str)> = Vec::new(); + let mut attributes: crate::structs::AttrCollection = Vec::new(); if self.horizontal.has_value() { - attributes.push(("horizontal", self.horizontal.get_value_string())); + attributes.push(("horizontal", self.horizontal.value_string()).into()); } if self.vertical.has_value() { - attributes.push(("vertical", self.vertical.get_value_string())); + attributes.push(("vertical", self.vertical.value_string()).into()); } if self.wrap_text.has_value() { - attributes.push(("wrapText", self.wrap_text.get_value_string())); + attributes.push(("wrapText", self.wrap_text.value_string()).into()); } - let text_rotation = self.text_rotation.get_value_string(); + let text_rotation = self.text_rotation.value_string(); if self.text_rotation.has_value() { - attributes.push(("textRotation", &text_rotation)); + attributes.push(("textRotation", text_rotation).into()); } write_start_tag(writer, "alignment", attributes, true); } diff --git a/src/structs/anchor.rs b/src/structs/anchor.rs index 1ee0c426..cb508761 100644 --- a/src/structs/anchor.rs +++ b/src/structs/anchor.rs @@ -1,19 +1,27 @@ #[derive(Default, Debug, Clone)] pub struct Anchor { - left_column: u32, - left_offset: u32, - top_row: u32, - top_offset: u32, - right_column: u32, - right_offset: u32, - bottom_row: u32, + left_column: u32, + left_offset: u32, + top_row: u32, + top_offset: u32, + right_column: u32, + right_offset: u32, + bottom_row: u32, bottom_offset: u32, } impl Anchor { #[inline] - pub fn get_left_column(&self) -> &u32 { - &self.left_column + #[must_use] + pub fn left_column(&self) -> u32 { + self.left_column + } + + #[inline] + #[must_use] + #[deprecated(since = "3.0.0", note = "Use left_column()")] + pub fn get_left_column(&self) -> u32 { + self.left_column() } #[inline] @@ -22,8 +30,16 @@ impl Anchor { } #[inline] - pub fn get_left_offset(&self) -> &u32 { - &self.left_offset + #[must_use] + pub fn left_offset(&self) -> u32 { + self.left_offset + } + + #[inline] + #[must_use] + #[deprecated(since = "3.0.0", note = "Use left_offset()")] + pub fn get_left_offset(&self) -> u32 { + self.left_offset() } #[inline] @@ -32,8 +48,16 @@ impl Anchor { } #[inline] - pub fn get_top_row(&self) -> &u32 { - &self.top_row + #[must_use] + pub fn top_row(&self) -> u32 { + self.top_row + } + + #[inline] + #[must_use] + #[deprecated(since = "3.0.0", note = "Use top_row()")] + pub fn get_top_row(&self) -> u32 { + self.top_row() } #[inline] @@ -42,8 +66,16 @@ impl Anchor { } #[inline] - pub fn get_top_offset(&self) -> &u32 { - &self.top_offset + #[must_use] + pub fn top_offset(&self) -> u32 { + self.top_offset + } + + #[inline] + #[must_use] + #[deprecated(since = "3.0.0", note = "Use top_offset()")] + pub fn get_top_offset(&self) -> u32 { + self.top_offset() } #[inline] @@ -52,18 +84,34 @@ impl Anchor { } #[inline] - pub fn get_right_column(&self) -> &u32 { - &self.right_column + #[must_use] + pub fn right_column(&self) -> u32 { + self.right_column } + #[inline] + #[must_use] + #[deprecated(since = "3.0.0", note = "Use right_column()")] + pub fn get_right_column(&self) -> u32 { + self.right_column() + } + #[inline] pub fn set_right_column(&mut self, value: u32) { self.right_column = value; } #[inline] - pub fn get_right_offset(&self) -> &u32 { - &self.right_offset + #[must_use] + pub fn right_offset(&self) -> u32 { + self.right_offset + } + + #[inline] + #[must_use] + #[deprecated(since = "3.0.0", note = "Use right_offset()")] + pub fn get_right_offset(&self) -> u32 { + self.right_offset() } #[inline] @@ -72,8 +120,16 @@ impl Anchor { } #[inline] - pub fn get_bottom_row(&self) -> &u32 { - &self.bottom_row + #[must_use] + pub fn bottom_row(&self) -> u32 { + self.bottom_row + } + + #[inline] + #[must_use] + #[deprecated(since = "3.0.0", note = "Use bottom_row()")] + pub fn get_bottom_row(&self) -> u32 { + self.bottom_row() } #[inline] @@ -82,8 +138,16 @@ impl Anchor { } #[inline] - pub fn get_bottom_offset(&self) -> &u32 { - &self.bottom_offset + #[must_use] + pub fn bottom_offset(&self) -> u32 { + self.bottom_offset + } + + #[inline] + #[must_use] + #[deprecated(since = "3.0.0", note = "Use bottom_offset()")] + pub fn get_bottom_offset(&self) -> u32 { + self.bottom_offset() } #[inline] @@ -92,26 +156,26 @@ impl Anchor { } #[inline] - pub(crate) fn _adjustment_insert_row(&mut self, num_rows: &u32) { + pub(crate) fn adjustment_insert_row(&mut self, num_rows: u32) { self.top_row += num_rows; self.bottom_row += num_rows; } #[inline] - pub(crate) fn _adjustment_insert_column(&mut self, num_cols: &u32) { + pub(crate) fn adjustment_insert_column(&mut self, num_cols: u32) { self.left_column += num_cols; self.right_column += num_cols; } #[inline] - pub(crate) fn _adjustment_remove_row(&mut self, num_rows: &u32) { - self.top_row = self.top_row.saturating_sub(*num_rows).max(1); - self.bottom_row = self.bottom_row.saturating_sub(*num_rows).max(1); + pub(crate) fn adjustment_remove_row(&mut self, num_rows: u32) { + self.top_row = self.top_row.saturating_sub(num_rows).max(1); + self.bottom_row = self.bottom_row.saturating_sub(num_rows).max(1); } #[inline] - pub(crate) fn _adjustment_remove_column(&mut self, num_cols: &u32) { - self.left_column = self.left_column.saturating_sub(*num_cols).max(1); - self.right_column = self.right_column.saturating_sub(*num_cols).max(1); + pub(crate) fn adjustment_remove_column(&mut self, num_cols: u32) { + self.left_column = self.left_column.saturating_sub(num_cols).max(1); + self.right_column = self.right_column.saturating_sub(num_cols).max(1); } } diff --git a/src/structs/attributes.rs b/src/structs/attributes.rs new file mode 100644 index 00000000..38512d2f --- /dev/null +++ b/src/structs/attributes.rs @@ -0,0 +1,140 @@ +/// Represents a pair of attribute name and value. +/// +/// This struct is used to hold a pair of attribute name and value. The +/// attribute name is a reference to a string slice, and the attribute value is +/// a `Cow` (Copy-on-Write) reference to a string. This allows for efficient +/// handling of both borrowed and owned strings. +/// +/// # Fields +/// +/// * `0`: The attribute name as a reference to a string slice. +/// * `1`: The attribute value as a `Cow` reference to a string. +pub struct AttrPair<'a>(pub(crate) &'a str, pub(crate) std::borrow::Cow<'a, str>); + +/// A collection of attribute pairs. +/// +/// This type alias represents a vector of `AttrPair` instances, which can be +/// used to collect and manage multiple attribute pairs. +pub type AttrCollection<'a> = Vec>; + +impl<'a> From<(&'a str, &'a str)> for AttrPair<'a> { + /// Converts a tuple of two string slices into an `AttrPair`. + /// + /// This method takes a tuple of two string slices and returns an `AttrPair` + /// instance. The first element of the tuple becomes the attribute name, and + /// the second element becomes the attribute value as a borrowed string. + /// + /// # Example + /// + /// ``` + /// let attr_pair = AttrPair::from(("name", "value")); + /// ``` + #[inline] + fn from(tuple: (&'a str, &'a str)) -> Self { + AttrPair(tuple.0, std::borrow::Cow::Borrowed(tuple.1)) + } +} + +impl<'a> From<(&'a str, String)> for AttrPair<'a> { + /// Converts a tuple of a string slice and a `String` into an `AttrPair`. + /// + /// This method takes a tuple of a string slice and a `String` and returns + /// an `AttrPair` instance. The string slice becomes the attribute name, and + /// the `String` becomes the attribute value as an owned string. + /// + /// # Example + /// + /// ``` + /// let attr_pair = AttrPair::from(("name", String::from("value"))); + /// ``` + #[inline] + fn from(tuple: (&'a str, String)) -> Self { + AttrPair(tuple.0, std::borrow::Cow::Owned(tuple.1)) + } +} + +impl<'a> From<(&'a str, &String)> for AttrPair<'a> { + /// Converts a tuple of a string slice and a reference to a `String` into an + /// `AttrPair`. + /// + /// This method takes a tuple of a string slice and a reference to a + /// `String` and returns an `AttrPair` instance. The string slice becomes + /// the attribute name, and the `String` becomes the attribute value as an + /// owned string. + /// + /// # Example + /// + /// ``` + /// let string = String::from("value"); + /// let attr_pair = AttrPair::from(("name", &string)); + /// ``` + #[inline] + fn from(tuple: (&'a str, &String)) -> Self { + AttrPair(tuple.0, std::borrow::Cow::Owned(tuple.1.to_owned())) + } +} + +impl<'a> From<(&'a str, Box)> for AttrPair<'a> { + /// Converts a tuple of a string slice and a `Box` of a string slice into an + /// `AttrPair`. + /// + /// This method takes a tuple of a string slice and a `Box` of a string + /// slice and returns an `AttrPair` instance. The string slice becomes the + /// attribute name, and the `Box` of a string slice becomes the attribute + /// value as an owned string. + /// + /// # Example + /// + /// ``` + /// let box_str = Box::new("value"); + /// let attr_pair = AttrPair::from(("name", box_str)); + /// ``` + #[inline] + fn from(tuple: (&'a str, Box)) -> Self { + AttrPair(tuple.0, std::borrow::Cow::Owned(tuple.1.into_string())) + } +} + +impl<'a> From<(&'a str, &Box)> for AttrPair<'a> { + /// Converts a tuple of a string slice and a reference to a `Box` of a + /// string slice into an `AttrPair`. + /// + /// This method takes a tuple of a string slice and a reference to a `Box` + /// of a string slice and returns an `AttrPair` instance. The string slice + /// becomes the attribute name, and the `Box` of a string slice becomes the + /// attribute value as an owned string. + /// + /// # Example + /// + /// ``` + /// let box_str = Box::new("value"); + /// let attr_pair = AttrPair::from(("name", &box_str)); + /// ``` + #[inline] + fn from(tuple: (&'a str, &Box)) -> Self { + AttrPair( + tuple.0, + std::borrow::Cow::Owned(tuple.1.clone().into_string()), + ) + } +} + +impl<'a> From> for (&'a str, std::borrow::Cow<'a, str>) { + /// Converts an `AttrPair` into a tuple of a string slice and a `Cow` of a + /// string slice. + /// + /// This method takes an `AttrPair` and returns a tuple of a string slice + /// and a `Cow` of a string slice. The attribute name becomes the string + /// slice, and the attribute value becomes the `Cow` of a string slice. + /// + /// # Example + /// + /// ``` + /// let attr_pair = AttrPair::from(("name", "value")); + /// let tuple = <(&str, Cow)>::from(attr_pair); + /// ``` + #[inline] + fn from(attr_pair: AttrPair<'a>) -> Self { + (attr_pair.0, attr_pair.1) + } +} diff --git a/src/structs/auto_filter.rs b/src/structs/auto_filter.rs index 8fa7381a..164dd734 100644 --- a/src/structs/auto_filter.rs +++ b/src/structs/auto_filter.rs @@ -8,15 +8,29 @@ pub struct AutoFilter { impl AutoFilter { #[inline] - pub fn get_range(&self) -> &Range { + #[must_use] + pub fn range(&self) -> &Range { &self.range } #[inline] - pub fn get_range_mut(&mut self) -> &mut Range { + #[must_use] + #[deprecated(since = "3.0.0", note = "Use range()")] + pub fn get_range(&self) -> &Range { + self.range() + } + + #[inline] + pub fn range_mut(&mut self) -> &mut Range { &mut self.range } + #[inline] + #[deprecated(since = "3.0.0", note = "Use range_mut()")] + pub fn get_range_mut(&mut self) -> &mut Range { + self.range_mut() + } + #[inline] pub(crate) fn set_range>(&mut self, value: S) { let mut range = Range::default(); @@ -28,10 +42,10 @@ impl AdjustmentCoordinate for AutoFilter { #[inline] fn adjustment_insert_coordinate( &mut self, - root_col_num: &u32, - offset_col_num: &u32, - root_row_num: &u32, - offset_row_num: &u32, + root_col_num: u32, + offset_col_num: u32, + root_row_num: u32, + offset_row_num: u32, ) { self.range.adjustment_insert_coordinate( root_col_num, @@ -44,10 +58,10 @@ impl AdjustmentCoordinate for AutoFilter { #[inline] fn adjustment_remove_coordinate( &mut self, - root_col_num: &u32, - offset_col_num: &u32, - root_row_num: &u32, - offset_row_num: &u32, + root_col_num: u32, + offset_col_num: u32, + root_row_num: u32, + offset_row_num: u32, ) { self.range.adjustment_remove_coordinate( root_col_num, diff --git a/src/structs/bold.rs b/src/structs/bold.rs index c8b880df..04eddb97 100644 --- a/src/structs/bold.rs +++ b/src/structs/bold.rs @@ -1,12 +1,21 @@ // b -use super::BooleanValue; -use crate::reader::driver::*; -use crate::writer::driver::*; -use quick_xml::events::BytesStart; -use quick_xml::Reader; -use quick_xml::Writer; use std::io::Cursor; +use quick_xml::{ + Reader, + Writer, + events::BytesStart, +}; + +use super::BooleanValue; +use crate::{ + reader::driver::{ + get_attribute, + set_string_from_xml, + }, + writer::driver::write_start_tag, +}; + #[derive(Clone, Default, Debug, Eq, Ord, PartialEq, PartialOrd)] pub struct Bold { pub(crate) val: BooleanValue, @@ -14,8 +23,16 @@ pub struct Bold { impl Bold { #[inline] - pub fn get_val(&self) -> &bool { - self.val.get_value() + #[must_use] + pub fn val(&self) -> bool { + self.val.value() + } + + #[inline] + #[must_use] + #[deprecated(since = "3.0.0", note = "Use val()")] + pub fn get_val(&self) -> bool { + self.val() } #[inline] @@ -37,7 +54,7 @@ impl Bold { #[inline] pub(crate) fn write_to(&self, writer: &mut Writer>>) { // b - if *self.val.get_value() { + if self.val.value() { write_start_tag(writer, "b", vec![], true); } } diff --git a/src/structs/boolean_value.rs b/src/structs/boolean_value.rs index 51bb0c2f..5692641d 100644 --- a/src/structs/boolean_value.rs +++ b/src/structs/boolean_value.rs @@ -5,16 +5,25 @@ pub struct BooleanValue { impl BooleanValue { #[inline] - pub(crate) fn get_value(&self) -> &bool { - self.value.as_ref().unwrap_or(&false) + pub(crate) fn value(&self) -> bool { + self.value.unwrap_or(false) } #[inline] + #[deprecated(since = "3.0.0", note = "Use value()")] + pub(crate) fn get_value(&self) -> bool { + self.value() + } + + #[inline] + pub(crate) fn value_string(&self) -> &str { + if self.value() { "1" } else { "0" } + } + + #[inline] + #[deprecated(since = "3.0.0", note = "Use value_string()")] pub(crate) fn get_value_string(&self) -> &str { - match *self.get_value() { - true => "1", - false => "0", - } + self.value_string() } #[inline] @@ -34,10 +43,16 @@ impl BooleanValue { } #[inline] - pub(crate) fn get_hash_string(&self) -> &str { + pub(crate) fn hash_string(&self) -> &str { if self.has_value() { - return self.get_value_string(); + return self.value_string(); } "empty!!" } + + #[inline] + #[deprecated(since = "3.0.0", note = "Use hash_string()")] + pub(crate) fn get_hash_string(&self) -> &str { + self.hash_string() + } } diff --git a/src/structs/border.rs b/src/structs/border.rs index 5e0150d3..97037f5d 100644 --- a/src/structs/border.rs +++ b/src/structs/border.rs @@ -1,41 +1,86 @@ // left right top bottom -use super::BorderStyleValues; -use super::Color; -use super::EnumValue; -use crate::reader::driver::*; -use crate::writer::driver::*; -use md5::Digest; -use quick_xml::events::{BytesStart, Event}; -use quick_xml::Reader; -use quick_xml::Writer; use std::io::Cursor; +use md5::Digest; +use quick_xml::{ + Reader, + Writer, + events::{ + BytesStart, + Event, + }, +}; + +use super::{ + BorderStyleValues, + Color, + EnumValue, +}; +use crate::{ + reader::driver::{ + get_attribute, + set_string_from_xml, + xml_read_loop, + }, + writer::driver::{ + write_end_tag, + write_start_tag, + }, +}; + #[derive(Default, Debug, Clone, PartialEq, PartialOrd)] pub struct Border { - color: Color, + color: Option>, style: EnumValue, } impl Border { + pub const BORDER_DASHDOT: &'static str = "dashDot"; + pub const BORDER_DASHDOTDOT: &'static str = "dashDotDot"; + pub const BORDER_DASHED: &'static str = "dashed"; + pub const BORDER_DOTTED: &'static str = "dotted"; + pub const BORDER_DOUBLE: &'static str = "double"; + pub const BORDER_HAIR: &'static str = "hair"; + pub const BORDER_MEDIUM: &'static str = "medium"; + pub const BORDER_MEDIUMDASHDOT: &'static str = "mediumDashDot"; + pub const BORDER_MEDIUMDASHDOTDOT: &'static str = "mediumDashDotDot"; + pub const BORDER_MEDIUMDASHED: &'static str = "mediumDashed"; + // Border style + pub const BORDER_NONE: &'static str = "none"; + pub const BORDER_SLANTDASHDOT: &'static str = "slantDashDot"; + pub const BORDER_THICK: &'static str = "thick"; + pub const BORDER_THIN: &'static str = "thin"; + #[inline] - pub fn get_color(&self) -> &Color { - &self.color + #[must_use] + pub fn color(&self) -> Option { + self.color.as_ref().map(|v| *v.clone()) } #[inline] - pub fn get_color_mut(&mut self) -> &mut Color { - &mut self.color + #[must_use] + #[deprecated(since = "3.0.0", note = "Use color()")] + pub fn get_color(&self) -> Option { + self.color() } #[inline] pub fn set_color(&mut self, value: Color) -> &mut Self { - self.color = value; + self.color = Some(Box::new(value)); self } #[inline] + #[must_use] + pub fn style(&self) -> &BorderStyleValues { + self.style.value() + } + + #[inline] + #[must_use] + #[deprecated(since = "3.0.0", note = "Use style()")] pub fn get_style(&self) -> &BorderStyleValues { - self.style.get_value() + self.style() } #[inline] @@ -44,46 +89,44 @@ impl Border { self } - // Border style - pub const BORDER_NONE: &'static str = "none"; - pub const BORDER_DASHDOT: &'static str = "dashDot"; - pub const BORDER_DASHDOTDOT: &'static str = "dashDotDot"; - pub const BORDER_DASHED: &'static str = "dashed"; - pub const BORDER_DOTTED: &'static str = "dotted"; - pub const BORDER_DOUBLE: &'static str = "double"; - pub const BORDER_HAIR: &'static str = "hair"; - pub const BORDER_MEDIUM: &'static str = "medium"; - pub const BORDER_MEDIUMDASHDOT: &'static str = "mediumDashDot"; - pub const BORDER_MEDIUMDASHDOTDOT: &'static str = "mediumDashDotDot"; - pub const BORDER_MEDIUMDASHED: &'static str = "mediumDashed"; - pub const BORDER_SLANTDASHDOT: &'static str = "slantDashDot"; - pub const BORDER_THICK: &'static str = "thick"; - pub const BORDER_THIN: &'static str = "thin"; + #[inline] + #[must_use] + pub fn border_style(&self) -> &str { + self.style.value_string() + } #[inline] + #[must_use] + #[deprecated(since = "3.0.0", note = "Use border_style()")] pub fn get_border_style(&self) -> &str { - self.style.get_value_string() + self.border_style() } + #[inline] pub fn set_border_style>(&mut self, value: S) { self.style.set_value_string(value); } - pub(crate) fn get_hash_code(&self) -> String { + pub(crate) fn hash_code(&self) -> String { format!( "{:x}", md5::Md5::digest(format!( "{}{}", - &self.style.get_value_string(), - &self.get_color().get_hash_code() + &self.style.value_string(), + &self.color().unwrap_or_default().argb_str() )) ) } + #[deprecated(since = "3.0.0", note = "Use hash_code()")] + pub(crate) fn get_hash_code(&self) -> String { + self.hash_code() + } + // When opened in software such as Excel, it is visually blank. #[inline] pub(crate) fn is_visually_empty(&self) -> bool { - self.style.get_value() == &BorderStyleValues::None + self.style.value() == &BorderStyleValues::None } pub(crate) fn set_attributes( @@ -102,17 +145,17 @@ impl Border { reader, Event::Empty(ref e) => { if e.name().into_inner() == b"color" { - self.color.set_attributes(reader, e, true); + self.color.clone().unwrap_or_default().set_attributes(reader, e, true); } }, Event::End(ref e) => { match e.name().into_inner() { - b"left" => return, - b"right" => return, - b"top" => return, - b"bottom" => return, - b"diagonal" => return, - b"vertical" => return, + b"left" | + b"right" | + b"top" | + b"bottom" | + b"diagonal" | + b"vertical" | b"horizontal" => return, _ => (), } @@ -161,17 +204,20 @@ impl Border { pub(crate) fn write_to(&self, writer: &mut Writer>>, tag_name: &str) { // left,right,top,bottom,diagonal,vertical,horizontal - let mut attributes: Vec<(&str, &str)> = Vec::new(); + let mut attributes: crate::structs::AttrCollection<'_> = Vec::new(); if self.style.has_value() { - attributes.push(("style", self.style.get_value_string())); + attributes.push(("style", self.style.value_string()).into()); } - let empty_flag = !self.color.has_value(); + let empty_flag = self.color.is_none(); write_start_tag(writer, tag_name, attributes, empty_flag); if !empty_flag { // color - self.color.write_to_color(writer); + self.color + .clone() + .unwrap_or_default() + .write_to_color(writer); write_end_tag(writer, tag_name); } diff --git a/src/structs/border_properties_type.rs b/src/structs/border_properties_type.rs index 7c142a87..4f445a94 100644 --- a/src/structs/border_properties_type.rs +++ b/src/structs/border_properties_type.rs @@ -1,14 +1,16 @@ -use super::BorderStyleValues; -use super::Color; +use super::{ + BorderStyleValues, + Color, +}; pub trait BorderPropertiesType { - fn get_color(&self) -> &Color; + fn color(&self) -> &Color; - fn get_color_mut(&mut self) -> &mut Color; + fn color_mut(&mut self) -> &mut Color; fn set_color(&mut self, value: Color) -> &mut Self; - fn get_style(&self) -> &BorderStyleValues; + fn style(&self) -> &BorderStyleValues; fn set_style(&mut self, value: BorderStyleValues) -> &mut Self; } diff --git a/src/structs/border_style_values.rs b/src/structs/border_style_values.rs index 07b54fdb..e5cc17db 100644 --- a/src/structs/border_style_values.rs +++ b/src/structs/border_style_values.rs @@ -1,5 +1,6 @@ -use super::EnumTrait; use std::str::FromStr; + +use super::EnumTrait; #[derive(Debug, Clone, Eq, Ord, PartialEq, PartialOrd)] pub enum BorderStyleValues { DashDot, @@ -25,7 +26,7 @@ impl Default for BorderStyleValues { } impl EnumTrait for BorderStyleValues { #[inline] - fn get_value_string(&self) -> &str { + fn value_string(&self) -> &str { match &self { Self::DashDot => "dashDot", Self::DashDotDot => "dashDotDot", diff --git a/src/structs/borders.rs b/src/structs/borders.rs index 1be79db4..84b5257f 100644 --- a/src/structs/borders.rs +++ b/src/structs/borders.rs @@ -1,292 +1,203 @@ // border -use super::BooleanValue; -use super::Border; -use crate::reader::driver::*; -use crate::writer::driver::*; -use md5::Digest; -use quick_xml::events::{BytesStart, Event}; -use quick_xml::Reader; -use quick_xml::Writer; use std::io::Cursor; +use md5::Digest; +use quick_xml::{ + Reader, + Writer, + events::{ + BytesStart, + Event, + }, +}; + +use super::{ + BooleanValue, + Border, +}; +use crate::{ + reader::driver::{ + get_attribute, + set_string_from_xml, + xml_read_loop, + }, + writer::driver::{ + write_end_tag, + write_start_tag, + }, +}; + +use paste::paste; + +pub(crate) enum BordersIndex { + Left = 0, + Right = 1, + Top = 2, + Bottom = 3, + Diagonal = 4, + Vertical = 5, + Horizontal = 6, +} + #[derive(Default, Debug, Clone, PartialEq, PartialOrd)] pub struct Borders { - left_border: Border, - right_border: Border, - top_border: Border, - bottom_border: Border, - diagonal_border: Border, - vertical_border: Border, - horizontal_border: Border, + data: Box<[Border; 7]>, diagonal_down: BooleanValue, - diagonal_up: BooleanValue, + diagonal_up: BooleanValue, } -impl Borders { - // Border style - pub const BORDER_NONE: &'static str = "none"; - pub const BORDER_DASHDOT: &'static str = "dashDot"; - pub const BORDER_DASHDOTDOT: &'static str = "dashDotDot"; - pub const BORDER_DASHED: &'static str = "dashed"; - pub const BORDER_DOTTED: &'static str = "dotted"; - pub const BORDER_DOUBLE: &'static str = "double"; - pub const BORDER_HAIR: &'static str = "hair"; - pub const BORDER_MEDIUM: &'static str = "medium"; - pub const BORDER_MEDIUMDASHDOT: &'static str = "mediumDashDot"; - pub const BORDER_MEDIUMDASHDOTDOT: &'static str = "mediumDashDotDot"; - pub const BORDER_MEDIUMDASHED: &'static str = "mediumDashed"; - pub const BORDER_SLANTDASHDOT: &'static str = "slantDashDot"; - pub const BORDER_THICK: &'static str = "thick"; - pub const BORDER_THIN: &'static str = "thin"; - - #[inline] - pub fn get_left_border(&self) -> &Border { - &self.left_border - } - - #[inline] - pub fn get_left_border_mut(&mut self) -> &mut Border { - &mut self.left_border - } - - #[inline] - pub fn set_left_border(&mut self, value: Border) -> &mut Self { - self.left_border = value; - self - } - - #[inline] - pub fn get_left(&self) -> &Border { - &self.left_border - } - - #[inline] - pub fn get_left_mut(&mut self) -> &mut Border { - &mut self.left_border - } - - #[inline] - pub fn set_left(&mut self, value: Border) -> &mut Self { - self.left_border = value; - self - } - - #[inline] - pub fn get_right_border(&self) -> &Border { - &self.right_border - } - - #[inline] - pub fn get_right_border_mut(&mut self) -> &mut Border { - &mut self.right_border - } - - #[inline] - pub fn set_right_border(&mut self, value: Border) -> &mut Self { - self.right_border = value; - self - } - - #[inline] - pub fn get_right(&self) -> &Border { - &self.right_border - } - - #[inline] - pub fn get_right_mut(&mut self) -> &mut Border { - &mut self.right_border - } - - #[inline] - pub fn set_right(&mut self, value: Border) -> &mut Self { - self.right_border = value; - self - } - - #[inline] - pub fn get_top_border(&self) -> &Border { - &self.top_border - } - - #[inline] - pub fn get_top_border_mut(&mut self) -> &mut Border { - &mut self.top_border - } - - #[inline] - pub fn set_top_border(&mut self, value: Border) -> &mut Self { - self.top_border = value; - self - } - - #[inline] - pub fn get_top(&self) -> &Border { - &self.top_border - } - - #[inline] - pub fn get_top_mut(&mut self) -> &mut Border { - &mut self.top_border - } - - #[inline] - pub fn set_top(&mut self, value: Border) -> &mut Self { - self.top_border = value; - self - } - - #[inline] - pub fn get_bottom_border(&self) -> &Border { - &self.bottom_border - } - - #[inline] - pub fn get_bottom_border_mut(&mut self) -> &mut Border { - &mut self.bottom_border - } - - #[inline] - pub fn set_bottom_border(&mut self, value: Border) -> &mut Self { - self.bottom_border = value; - self - } - - #[inline] - pub fn get_bottom(&self) -> &Border { - &self.bottom_border - } - - #[inline] - pub fn get_bottom_mut(&mut self) -> &mut Border { - &mut self.bottom_border - } - - #[inline] - pub fn set_bottom(&mut self, value: Border) -> &mut Self { - self.bottom_border = value; - self - } - - #[inline] - pub fn get_diagonal_border(&self) -> &Border { - &self.diagonal_border +macro_rules! border_const { + ($($name:ident => $value:expr),*) => { + impl Borders { + $(pub const $name: &'static str = $value;)* + } } +} - #[inline] - pub fn get_diagonal_border_mut(&mut self) -> &mut Border { - &mut self.diagonal_border - } +macro_rules! border_accessors { + ($($fn_name:ident, $index:ident),*) => { + impl Borders { + paste! { + $( + pub fn [](&self) -> &Border { + &self.data[BordersIndex::$index as usize] + } - #[inline] - pub fn set_diagonal_border(&mut self, value: Border) -> &mut Self { - self.diagonal_border = value; - self - } + pub fn [](&mut self) -> &mut Border { + &mut self.data[BordersIndex::$index as usize] + } - #[inline] - pub fn get_diagonal(&self) -> &Border { - &self.diagonal_border - } + pub fn [](&mut self, value: Border) -> &mut Self { + self.data[BordersIndex::$index as usize] = value; + self + } - #[inline] - pub fn get_diagonal_mut(&mut self) -> &mut Border { - &mut self.diagonal_border - } + pub fn [](&self) -> &Border { + &self.data[BordersIndex::$index as usize] + } - #[inline] - pub fn set_diagonal(&mut self, value: Border) -> &mut Self { - self.diagonal_border = value; - self - } + pub fn [](&mut self) -> &mut Border { + &mut self.data[BordersIndex::$index as usize] + } - #[inline] - pub fn get_vertical_border(&self) -> &Border { - &self.vertical_border + pub fn [](&mut self, value: Border) -> &mut Self { + self.data[BordersIndex::$index as usize] = value; + self + } + )* + } + } } +} - #[inline] - pub fn get_vertical_border_mut(&mut self) -> &mut Border { - &mut self.vertical_border - } +border_const! { + BORDER_DASHDOT => "dashDot", + BORDER_DASHDOTDOT => "dashDotDot", + BORDER_DASHED => "dashed", + BORDER_DOTTED => "dotted", + BORDER_DOUBLE => "double", + BORDER_HAIR => "hair", + BORDER_MEDIUM => "medium", + BORDER_MEDIUMDASHDOT => "mediumDashDot", + BORDER_MEDIUMDASHDOTDOT => "mediumDashDotDot", + BORDER_MEDIUMDASHED => "mediumDashed", + BORDER_NONE => "none", + BORDER_SLANTDASHDOT => "slantDashDot", + BORDER_THICK => "thick", + BORDER_THIN => "thin" +} - #[inline] - pub fn set_vertical_border(&mut self, value: Border) -> &mut Self { - self.vertical_border = value; - self - } +border_accessors! { + left, Left, + right, Right, + top, Top, + bottom, Bottom, + diagonal, Diagonal, + vertical, Vertical, + horizontal, Horizontal +} +impl Borders { #[inline] - pub fn get_horizontal_border(&self) -> &Border { - &self.horizontal_border + pub fn diagonal_down(&self) -> bool { + self.diagonal_down.value() } #[inline] - pub fn get_horizontal_border_mut(&mut self) -> &mut Border { - &mut self.horizontal_border + #[deprecated(since = "3.0.0", note = "Use diagonal_down()")] + pub fn get_diagonal_down(&self) -> bool { + self.diagonal_down() } #[inline] - pub fn set_horizontal_border(&mut self, value: Border) -> &mut Self { - self.horizontal_border = value; - self + pub fn set_diagonal_down(&mut self, value: bool) { + self.diagonal_down.set_value(value); } #[inline] - pub fn get_diagonal_down(&self) -> &bool { - self.diagonal_down.get_value() + pub fn diagonal_up(&self) -> bool { + self.diagonal_up.value() } #[inline] - pub fn set_diagonal_down(&mut self, value: bool) { - self.diagonal_down.set_value(value); + #[deprecated(since = "3.0.0", note = "Use diagonal_up()")] + pub fn get_diagonal_up(&self) -> bool { + self.diagonal_up() } #[inline] - pub fn get_diagonal_up(&self) -> &bool { - self.diagonal_up.get_value() + pub fn set_diagonal_up(&mut self, value: bool) { + self.diagonal_up.set_value(value); } #[inline] - pub fn set_diagonal_up(&mut self, value: bool) { - self.diagonal_up.set_value(value); + pub(crate) fn default_value() -> Self { + Self::default() } #[inline] + #[deprecated(since = "3.0.0", note = "Use default_value()")] pub(crate) fn get_default_value() -> Self { Self::default() } #[inline] - pub(crate) fn get_hash_code(&self) -> String { + pub(crate) fn hash_code(&self) -> String { format!( "{:x}", md5::Md5::digest(format!( "{}{}{}{}{}{}{}{}{}", - &self.get_left_border().get_hash_code(), - &self.get_right_border().get_hash_code(), - &self.get_top_border().get_hash_code(), - &self.get_bottom_border().get_hash_code(), - &self.get_diagonal_border().get_hash_code(), - &self.get_vertical_border().get_hash_code(), - &self.get_horizontal_border().get_hash_code(), - &self.diagonal_down.get_value_string(), - &self.diagonal_up.get_value_string() + &self.get_left_border().hash_code(), + &self.get_right_border().hash_code(), + &self.get_top_border().hash_code(), + &self.get_bottom_border().hash_code(), + &self.get_diagonal_border().hash_code(), + &self.get_vertical_border().hash_code(), + &self.get_horizontal_border().hash_code(), + &self.diagonal_down.value_string(), + &self.diagonal_up.value_string() )) ) } - // When opened in software such as Excel, it is visually blank. + #[inline] + #[deprecated(since = "3.0.0", note = "Use hash_code()")] + pub(crate) fn get_hash_code(&self) -> String { + self.hash_code() + } + #[inline] pub(crate) fn is_visually_empty(&self) -> bool { - self.left_border.is_visually_empty() - || self.right_border.is_visually_empty() - || self.top_border.is_visually_empty() - || self.bottom_border.is_visually_empty() - || self.diagonal_border.is_visually_empty() - || self.vertical_border.is_visually_empty() - || self.horizontal_border.is_visually_empty() + self.data[BordersIndex::Left as usize].is_visually_empty() + || self.data[BordersIndex::Right as usize].is_visually_empty() + || self.data[BordersIndex::Top as usize].is_visually_empty() + || self.data[BordersIndex::Bottom as usize].is_visually_empty() + || self.data[BordersIndex::Diagonal as usize].is_visually_empty() + || self.data[BordersIndex::Vertical as usize].is_visually_empty() + || self.data[BordersIndex::Horizontal as usize].is_visually_empty() } + #[inline] pub(crate) fn set_attributes( &mut self, reader: &mut Reader, @@ -300,25 +211,25 @@ impl Borders { Event::Empty(ref e) => { match e.name().into_inner() { b"left" => { - self.left_border.set_attributes(reader, e, true); + self.data[BordersIndex::Left as usize].set_attributes(reader, e, true); } b"right" => { - self.right_border.set_attributes(reader, e, true); + self.data[BordersIndex::Right as usize].set_attributes(reader, e, true); } b"top" => { - self.top_border.set_attributes(reader, e, true); + self.data[BordersIndex::Top as usize].set_attributes(reader, e, true); } b"bottom" => { - self.bottom_border.set_attributes(reader, e, true); + self.data[BordersIndex::Bottom as usize].set_attributes(reader, e, true); } b"diagonal" => { - self.diagonal_border.set_attributes(reader, e, true); + self.data[BordersIndex::Diagonal as usize].set_attributes(reader, e, true); } b"vertical" => { - self.vertical_border.set_attributes(reader, e, true); + self.data[BordersIndex::Vertical as usize].set_attributes(reader, e, true); } b"horizontal" => { - self.horizontal_border.set_attributes(reader, e, true); + self.data[BordersIndex::Horizontal as usize].set_attributes(reader, e, true); } _ => (), } @@ -326,25 +237,25 @@ impl Borders { Event::Start(ref e) => { match e.name().into_inner() { b"left" => { - self.left_border.set_attributes(reader, e, false); + self.data[BordersIndex::Left as usize].set_attributes(reader, e, false); } b"right" => { - self.right_border.set_attributes(reader, e, false); + self.data[BordersIndex::Right as usize].set_attributes(reader, e, false); } b"top" => { - self.top_border.set_attributes(reader, e, false); + self.data[BordersIndex::Top as usize].set_attributes(reader, e, false); } b"bottom" => { - self.bottom_border.set_attributes(reader, e, false); + self.data[BordersIndex::Bottom as usize].set_attributes(reader, e, false); } b"diagonal" => { - self.diagonal_border.set_attributes(reader, e, false); + self.data[BordersIndex::Diagonal as usize].set_attributes(reader, e, false); } b"vertical" => { - self.vertical_border.set_attributes(reader, e, false); + self.data[BordersIndex::Vertical as usize].set_attributes(reader, e, false); } b"horizontal" => { - self.horizontal_border.set_attributes(reader, e, false); + self.data[BordersIndex::Horizontal as usize].set_attributes(reader, e, false); } _ => (), } @@ -358,40 +269,43 @@ impl Borders { ); } + #[inline] pub(crate) fn write_to(&self, writer: &mut Writer>>) { // border - let mut attributes: Vec<(&str, &str)> = Vec::new(); + let mut attributes: crate::structs::AttrCollection = Vec::new(); if self.diagonal_up.has_value() { - attributes.push(("diagonalUp", self.diagonal_up.get_value_string())); + attributes.push(("diagonalUp", self.diagonal_up.value_string()).into()); } if self.diagonal_down.has_value() { - attributes.push(("diagonalDown", self.diagonal_down.get_value_string())); + attributes.push(("diagonalDown", self.diagonal_down.value_string()).into()); } write_start_tag(writer, "border", attributes, false); // left - self.left_border.write_to_left(writer); + self.data[BordersIndex::Left as usize].write_to_left(writer); // right - self.right_border.write_to_right(writer); + self.data[BordersIndex::Right as usize].write_to_right(writer); // top - self.top_border.write_to_top(writer); + self.data[BordersIndex::Top as usize].write_to_top(writer); // bottom - self.bottom_border.write_to_bottom(writer); + self.data[BordersIndex::Bottom as usize].write_to_bottom(writer); // diagonal - self.diagonal_border.write_to_diagonal(writer); + self.data[BordersIndex::Diagonal as usize].write_to_diagonal(writer); // vertical - if self.vertical_border.get_hash_code() != Border::default().get_hash_code() { - self.vertical_border.write_to_vertical(writer); + if self.data[BordersIndex::Vertical as usize].hash_code() + != Border::default().hash_code() + { + self.data[BordersIndex::Vertical as usize].write_to_vertical(writer); } // horizontal - if self.horizontal_border.get_hash_code() != Border::default().get_hash_code() { - self.horizontal_border.write_to_horizontal(writer); + if self.data[BordersIndex::Horizontal as usize].hash_code() != Border::default().hash_code() { + self.data[BordersIndex::Horizontal as usize].write_to_horizontal(writer); } write_end_tag(writer, "border"); diff --git a/src/structs/borders_crate.rs b/src/structs/borders_crate.rs index 093b5006..0d31674c 100644 --- a/src/structs/borders_crate.rs +++ b/src/structs/borders_crate.rs @@ -1,30 +1,57 @@ // borders -use super::Borders; -use super::Style; -use crate::reader::driver::*; -use crate::writer::driver::*; -use quick_xml::events::{BytesStart, Event}; -use quick_xml::Reader; -use quick_xml::Writer; use std::io::Cursor; -use thin_vec::ThinVec; + +use quick_xml::{ + Reader, + Writer, + events::{ + BytesStart, + Event, + }, +}; + +use super::{ + Borders, + Style, +}; +use crate::{ + reader::driver::xml_read_loop, + writer::driver::{ + write_end_tag, + write_start_tag, + }, +}; #[derive(Clone, Default, Debug)] pub(crate) struct BordersCrate { - borders: ThinVec, + borders: Vec, } impl BordersCrate { #[inline] - pub(crate) fn get_borders(&self) -> &[Borders] { + pub(crate) fn borders(&self) -> &[Borders] { &self.borders } #[inline] - pub(crate) fn get_borders_mut(&mut self) -> &mut ThinVec { + #[deprecated(since = "3.0.0", note = "Use borders()")] + pub(crate) fn get_borders(&self) -> &[Borders] { + self.borders() + } + + #[inline] + #[allow(dead_code)] + pub(crate) fn borders_mut(&mut self) -> &mut Vec { &mut self.borders } + #[inline] + #[allow(dead_code)] + #[deprecated(since = "3.0.0", note = "Use borders_mut()")] + pub(crate) fn get_borders_mut(&mut self) -> &mut Vec { + self.borders_mut() + } + #[inline] pub(crate) fn set_borders(&mut self, value: Borders) -> &mut Self { self.borders.push(value); @@ -32,12 +59,12 @@ impl BordersCrate { } pub(crate) fn set_style(&mut self, style: &Style) -> u32 { - match style.get_borders() { + match style.borders() { Some(v) => { - let hash_code = v.get_hash_code(); + let hash_code = v.hash_code(); let mut id = 0; for borders in &self.borders { - if borders.get_hash_code() == hash_code { + if borders.hash_code() == hash_code { return id; } id += 1; @@ -84,7 +111,7 @@ impl BordersCrate { write_start_tag( writer, "borders", - vec![("count", &self.borders.len().to_string())], + vec![("count", &self.borders.len().to_string()).into()], false, ); diff --git a/src/structs/break.rs b/src/structs/break.rs index d7c97e12..c29c42aa 100644 --- a/src/structs/break.rs +++ b/src/structs/break.rs @@ -1,25 +1,44 @@ // brk -use crate::reader::driver::*; -use crate::structs::BooleanValue; -use crate::structs::UInt32Value; -use crate::writer::driver::*; -use quick_xml::events::BytesStart; -use quick_xml::Reader; -use quick_xml::Writer; use std::io::Cursor; +use quick_xml::{ + Reader, + Writer, + events::BytesStart, +}; + +use crate::{ + reader::driver::{ + get_attribute, + set_string_from_xml, + }, + structs::{ + BooleanValue, + UInt32Value, + }, + writer::driver::write_start_tag, +}; + #[derive(Clone, Default, Debug)] pub struct Break { - id: UInt32Value, - max: UInt32Value, - min: UInt32Value, + id: UInt32Value, + max: UInt32Value, + min: UInt32Value, manual_page_break: BooleanValue, } impl Break { #[inline] - pub fn get_id(&self) -> &u32 { - self.id.get_value() + #[must_use] + pub fn id(&self) -> u32 { + self.id.value() + } + + #[inline] + #[must_use] + #[deprecated(since = "3.0.0", note = "Use id()")] + pub fn get_id(&self) -> u32 { + self.id() } #[inline] @@ -29,8 +48,16 @@ impl Break { } #[inline] - pub fn get_max(&self) -> &u32 { - self.max.get_value() + #[must_use] + pub fn max(&self) -> u32 { + self.max.value() + } + + #[inline] + #[must_use] + #[deprecated(since = "3.0.0", note = "Use max()")] + pub fn get_max(&self) -> u32 { + self.max() } #[inline] @@ -40,8 +67,16 @@ impl Break { } #[inline] - pub fn get_manual_page_break(&self) -> &bool { - self.manual_page_break.get_value() + #[must_use] + pub fn manual_page_break(&self) -> bool { + self.manual_page_break.value() + } + + #[inline] + #[must_use] + #[deprecated(since = "3.0.0", note = "Use manual_page_break()")] + pub fn get_manual_page_break(&self) -> bool { + self.manual_page_break() } #[inline] @@ -62,25 +97,26 @@ impl Break { set_string_from_xml!(self, e, manual_page_break, "man"); } + #[inline] pub(crate) fn write_to(&self, writer: &mut Writer>>) { // brk - let mut attributes: Vec<(&str, &str)> = Vec::new(); - let id = self.id.get_value_string(); - attributes.push(("id", &id)); + let mut attributes: crate::structs::AttrCollection = Vec::new(); + let id = self.id.value_string(); + attributes.push(("id", &id).into()); - let max = self.max.get_value_string(); + let max = self.max.value_string(); if self.max.has_value() { - attributes.push(("max", &max)); + attributes.push(("max", &max).into()); } - let min = self.min.get_value_string(); + let min = self.min.value_string(); if self.min.has_value() { - attributes.push(("min", &min)); + attributes.push(("min", &min).into()); } - let manual_page_break = self.manual_page_break.get_value_string(); + let manual_page_break = self.manual_page_break.value_string(); if self.manual_page_break.has_value() { - attributes.push(("man", manual_page_break)); + attributes.push(("man", manual_page_break).into()); } write_start_tag(writer, "brk", attributes, true); } diff --git a/src/structs/byte_value.rs b/src/structs/byte_value.rs index 86fd8ed6..2fdcee0a 100644 --- a/src/structs/byte_value.rs +++ b/src/structs/byte_value.rs @@ -4,16 +4,25 @@ pub struct ByteValue { } impl ByteValue { #[inline] - pub(crate) fn get_value(&self) -> &u8 { - match &self.value { - Some(v) => v, - None => &0, - } + pub(crate) fn value(&self) -> u8 { + self.value.unwrap_or(0) } #[inline] + #[deprecated(since = "3.0.0", note = "Use value()")] + pub(crate) fn get_value(&self) -> u8 { + self.value.unwrap_or(0) + } + + #[inline] + pub(crate) fn value_string(&self) -> String { + self.value().to_string() + } + + #[inline] + #[deprecated(since = "3.0.0", note = "Use value_string()")] pub(crate) fn get_value_string(&self) -> String { - self.get_value().to_string() + self.value_string() } #[inline] diff --git a/src/structs/cache_field.rs b/src/structs/cache_field.rs index 6a6e6ced..6b491493 100644 --- a/src/structs/cache_field.rs +++ b/src/structs/cache_field.rs @@ -1,52 +1,109 @@ // cacheField -use crate::reader::driver::*; -use crate::structs::SharedItems; -use crate::structs::StringValue; -use crate::structs::UInt32Value; -use crate::writer::driver::*; -use quick_xml::events::{BytesStart, Event}; -use quick_xml::Reader; -use quick_xml::Writer; use std::io::Cursor; +use quick_xml::{ + Reader, + Writer, + events::{ + BytesStart, + Event, + }, +}; + +use crate::{ + reader::driver::{ + get_attribute, + set_string_from_xml, + xml_read_loop, + }, + structs::{ + SharedItems, + StringValue, + UInt32Value, + }, + writer::driver::{ + write_end_tag, + write_start_tag, + }, +}; + #[derive(Clone, Default, Debug)] pub struct CacheField { - name: StringValue, + name: StringValue, number_format_id: UInt32Value, - shared_items: SharedItems, + shared_items: SharedItems, } impl CacheField { + #[inline] + #[must_use] + pub fn name(&self) -> &str { + self.name.value_str() + } + + #[inline] + #[must_use] + #[deprecated(since = "3.0.0", note = "Use name()")] pub fn get_name(&self) -> &str { - self.name.get_value_str() + self.name() } - pub(crate) fn set_name>(&mut self, value: S) -> &mut Self { + #[inline] + pub fn set_name>(&mut self, value: S) -> &mut Self { self.name.set_value(value); self } - pub fn get_number_format_id(&self) -> &u32 { - self.number_format_id.get_value() + #[inline] + #[must_use] + pub fn number_format_id(&self) -> u32 { + self.number_format_id.value() } + #[inline] + #[must_use] + #[deprecated(since = "3.0.0", note = "Use number_format_id()")] + pub fn get_number_format_id(&self) -> u32 { + self.number_format_id() + } + + #[inline] pub fn set_number_format_id(&mut self, value: u32) -> &mut Self { self.number_format_id.set_value(value); self } - pub fn get_shared_items(&self) -> &SharedItems { + #[inline] + #[must_use] + pub fn shared_items(&self) -> &SharedItems { &self.shared_items } - pub fn get_shared_items_mut(&mut self) -> &mut SharedItems { + #[inline] + #[must_use] + #[deprecated(since = "3.0.0", note = "Use shared_items()")] + pub fn get_shared_items(&self) -> &SharedItems { + self.shared_items() + } + + #[inline] + pub fn shared_items_mut(&mut self) -> &mut SharedItems { &mut self.shared_items } + #[inline] + #[deprecated(since = "3.0.0", note = "Use shared_items_mut()")] + pub fn get_shared_items_mut(&mut self) -> &mut SharedItems { + self.shared_items_mut() + } + + #[inline] pub fn set_shared_items(&mut self, value: SharedItems) -> &mut Self { self.shared_items = value; self } + #[inline] + #[allow(dead_code)] pub(crate) fn set_attributes( &mut self, reader: &mut Reader, @@ -73,17 +130,20 @@ impl CacheField { ); } + #[inline] + #[allow(dead_code)] pub(crate) fn write_to(&self, writer: &mut Writer>>) { - // pivotField + // cacheField write_start_tag( writer, - "pivotField", + "cacheField", vec![ - ("name", self.name.get_value_str()), + ("name", self.name.value_str()).into(), ( "numFmtId", - self.number_format_id.get_value_string().as_str(), - ), + &self.number_format_id.value_string(), + ) + .into(), ], false, ); @@ -91,6 +151,6 @@ impl CacheField { // sharedItems self.shared_items.write_to(writer); - write_end_tag(writer, "pivotField"); + write_end_tag(writer, "cacheField"); } } diff --git a/src/structs/cache_fields.rs b/src/structs/cache_fields.rs index b17f8376..3edbb69c 100644 --- a/src/structs/cache_fields.rs +++ b/src/structs/cache_fields.rs @@ -1,34 +1,61 @@ // cacheFields -use crate::reader::driver::*; -use crate::structs::BooleanValue; -use crate::structs::ByteValue; -use crate::structs::CacheField; -use crate::structs::StringValue; -use crate::structs::UInt32Value; -use crate::writer::driver::*; -use quick_xml::events::{BytesStart, Event}; -use quick_xml::Reader; -use quick_xml::Writer; use std::io::Cursor; +use quick_xml::{ + Reader, + Writer, + events::{ + BytesStart, + Event, + }, +}; + +use crate::{ + reader::driver::xml_read_loop, + structs::CacheField, + writer::driver::{ + write_end_tag, + write_start_tag, + }, +}; + #[derive(Clone, Default, Debug)] pub struct CacheFields { list: Vec, } impl CacheFields { - pub fn get_list(&self) -> &Vec { + #[inline] + #[must_use] + pub fn list(&self) -> &Vec { &self.list } - pub fn get_list_mut(&mut self) -> &mut Vec { + #[inline] + #[must_use] + #[deprecated(since = "3.0.0", note = "Use list()")] + pub fn get_list(&self) -> &Vec { + self.list() + } + + #[inline] + pub fn list_mut(&mut self) -> &mut Vec { &mut self.list } + #[inline] + #[deprecated(since = "3.0.0", note = "Use list_mut()")] + pub fn get_list_mut(&mut self) -> &mut Vec { + self.list_mut() + } + + #[inline] pub fn add_list_mut(&mut self, value: CacheField) -> &mut Self { self.list.push(value); self } + #[inline] + #[allow(dead_code, unused_variables)] pub(crate) fn set_attributes( &mut self, reader: &mut Reader, @@ -52,12 +79,14 @@ impl CacheFields { ); } + #[inline] + #[allow(dead_code)] pub(crate) fn write_to(&self, writer: &mut Writer>>) { // cacheFields write_start_tag( writer, "cacheFields", - vec![("count", self.list.len().to_string().as_str())], + vec![("count", self.list.len().to_string()).into()], false, ); diff --git a/src/structs/cache_source.rs b/src/structs/cache_source.rs index 8084b161..6ee107e1 100644 --- a/src/structs/cache_source.rs +++ b/src/structs/cache_source.rs @@ -1,45 +1,92 @@ // cacheSource -use crate::structs::EnumValue; -use crate::structs::SourceValues; -use crate::structs::WorksheetSource; - -use crate::helper::const_str::*; -use crate::reader::driver::*; -use crate::writer::driver::*; -use quick_xml::events::{BytesStart, Event}; -use quick_xml::Reader; -use quick_xml::Writer; use std::io::Cursor; +use quick_xml::{ + Reader, + Writer, + events::{ + BytesStart, + Event, + }, +}; + +use crate::{ + reader::driver::{ + get_attribute, + set_string_from_xml, + xml_read_loop, + }, + structs::{ + EnumValue, + SourceValues, + WorksheetSource, + }, + writer::driver::{ + write_end_tag, + write_start_tag, + }, +}; + #[derive(Clone, Default, Debug)] pub struct CacheSource { - r#type: EnumValue, + r#type: EnumValue, worksheet_source: Option, } impl CacheSource { + #[inline] + #[must_use] pub fn get_type(&self) -> &SourceValues { - self.r#type.get_value() + self.r#type.value() } + #[inline] pub fn set_type(&mut self, value: SourceValues) -> &mut Self { self.r#type.set_value(value); self } - pub fn get_worksheet_source(&self) -> Option<&WorksheetSource> { + #[inline] + #[must_use] + pub fn worksheet_source(&self) -> Option<&WorksheetSource> { self.worksheet_source.as_ref() } - pub fn get_worksheet_source_mut(&mut self) -> Option<&mut WorksheetSource> { + #[inline] + #[must_use] + #[deprecated(since = "3.0.0", note = "Use worksheet_source()")] + pub fn get_worksheet_source(&self) -> Option<&WorksheetSource> { + self.worksheet_source() + } + + #[inline] + pub fn worksheet_source_mut(&mut self) -> Option<&mut WorksheetSource> { self.worksheet_source.as_mut() } + #[inline] + #[deprecated(since = "3.0.0", note = "Use worksheet_source_mut()")] + pub fn get_worksheet_source_mut(&mut self) -> Option<&mut WorksheetSource> { + self.worksheet_source_mut() + } + + #[inline] pub fn set_worksheet_source_mut(&mut self, value: WorksheetSource) -> &mut Self { self.worksheet_source = Some(value); self } + /// Create a new worksheet cache source + #[must_use] + pub fn new_worksheet(worksheet_source: WorksheetSource) -> Self { + let mut cache_source = Self::default(); + cache_source.set_type(SourceValues::Worksheet); + cache_source.set_worksheet_source_mut(worksheet_source); + cache_source + } + + #[inline] + #[allow(dead_code)] pub(crate) fn set_attributes( &mut self, reader: &mut Reader, @@ -70,20 +117,21 @@ impl CacheSource { ); } + #[inline] + #[allow(dead_code)] pub(crate) fn write_to(&self, writer: &mut Writer>>) { // cacheSource let empty_flg = self.worksheet_source.is_none(); - let mut attributes: Vec<(&str, &str)> = Vec::new(); - attributes.push(("type", self.r#type.get_hash_string())); + let attributes: crate::structs::AttrCollection = + vec![("type", self.r#type.hash_string()).into()]; + write_start_tag(writer, "cacheSource", attributes, empty_flg); if !empty_flg { // worksheetSource - match &self.worksheet_source { - Some(v) => v.write_to(writer), - None => {} + if let Some(v) = &self.worksheet_source { + v.write_to(writer); } - write_end_tag(writer, "cacheSource"); } } diff --git a/src/structs/cell.rs b/src/structs/cell.rs index 5c8d8462..4d1efabf 100644 --- a/src/structs/cell.rs +++ b/src/structs/cell.rs @@ -1,82 +1,154 @@ -use crate::helper::coordinate::*; -use crate::helper::formula::*; -use crate::helper::number_format::*; -use crate::reader::driver::*; -use crate::structs::CellFormula; -use crate::structs::CellFormulaValues; -use crate::structs::CellRawValue; -use crate::structs::CellValue; -use crate::structs::Coordinate; -use crate::structs::Hyperlink; -use crate::structs::NumberingFormat; -use crate::structs::RichText; -use crate::structs::SharedStringItem; -use crate::structs::SharedStringTable; -use crate::structs::Style; -use crate::structs::Stylesheet; -use crate::structs::UInt32Value; -use crate::traits::AdjustmentCoordinate; -use crate::traits::AdjustmentCoordinateWith2Sheet; -use crate::writer::driver::*; -use quick_xml::events::{BytesStart, Event}; -use quick_xml::Reader; -use quick_xml::Writer; -use std::borrow::Cow; -use std::collections::HashMap; -use std::io::Cursor; -use std::sync::{Arc, RwLock}; +use std::{ + borrow::Cow, + collections::HashMap, + io::Cursor, + sync::RwLock, +}; + +use quick_xml::{ + Reader, + Writer, + events::{ + BytesStart, + Event, + }, +}; + +use crate::{ + helper::{ + coordinate::CellCoordinates, + formula::{ + FormulaToken, + adjustment_formula_coordinate, + parse_to_tokens, + render, + }, + number_format::to_formatted_string, + }, + reader::driver::{ + get_attribute, + set_string_from_xml, + }, + structs::{ + CellFormula, + CellFormulaValues, + CellRawValue, + CellValue, + Coordinate, + Hyperlink, + NumberingFormat, + RichText, + SharedStringItem, + SharedStringTable, + Style, + Stylesheet, + UInt32Value, + }, + traits::{ + AdjustmentCoordinate, + AdjustmentCoordinateWith2Sheet, + }, + writer::driver::{ + write_end_tag, + write_start_tag, + write_text_node, + write_text_node_conversion, + }, +}; #[derive(Clone, Default, Debug, PartialEq, PartialOrd)] pub struct Cell { - coordinate: Coordinate, + coordinate: Coordinate, pub(crate) cell_value: Box, - style: Box