|
| 1 | +//! This module provides functionality for managing Cyrillic letter counters for both |
| 2 | +//! uppercase and lowercase letters, following Excel's alphabetic counter style. |
| 3 | +
|
| 4 | +use super::{CounterFormatter, LOWERCASE, UPPERCASE, write_number_as_letters_gen}; |
| 5 | +use std::fmt; |
| 6 | + |
| 7 | +/// An array of uppercase Cyrillic letters used for indexing and mapping. |
| 8 | +/// This array includes all uppercase Cyrillic letters excluding 'Ё', 'Й', 'Ъ', 'Ы', 'Ь'. |
| 9 | +const UPPERCASE_CYRILLIC: [char; 28] = [ |
| 10 | + 'А', 'Б', 'В', 'Г', 'Д', 'Е', 'Ж', 'З', 'И', 'К', 'Л', 'М', 'Н', 'О', 'П', 'Р', 'С', 'Т', 'У', |
| 11 | + 'Ф', 'Х', 'Ц', 'Ч', 'Ш', 'Щ', 'Э', 'Ю', 'Я', |
| 12 | +]; |
| 13 | + |
| 14 | +/// An array of lowercase Cyrillic letters used for indexing and mapping. |
| 15 | +/// This array includes all lowercase Cyrillic letters excluding 'ё', 'й', 'ъ', 'ы', 'ь'. |
| 16 | +const LOWERCASE_CYRILLIC: [char; 28] = [ |
| 17 | + 'а', 'б', 'в', 'г', 'д', 'е', 'ж', 'з', 'и', 'к', 'л', 'м', 'н', 'о', 'п', 'р', 'с', 'т', 'у', |
| 18 | + 'ф', 'х', 'ц', 'ч', 'ш', 'щ', 'э', 'ю', 'я', |
| 19 | +]; |
| 20 | + |
| 21 | +/// A helper structure for generating uppercase Cyrillic letters (e.g., А, Б, В, ..., АА, АБ), |
| 22 | +/// while excluding 'Ё', 'Й', 'Ъ', 'Ы' and 'Ь'. |
| 23 | +#[derive(Debug, Clone, Copy, PartialEq, Eq)] |
| 24 | +pub struct CyrillicUpper; |
| 25 | + |
| 26 | +/// A helper structure for generating lowercase Cyrillic letters (e.g., а, б, в, ..., аа, аб), |
| 27 | +/// while excluding 'ё', 'й', 'ъ', 'ы' and 'ь'. |
| 28 | +#[derive(Debug, Clone, Copy, PartialEq, Eq)] |
| 29 | +pub struct CyrillicLower; |
| 30 | + |
| 31 | +impl_counter_formatter! { CyrillicUpper, UPPERCASE } |
| 32 | +impl_counter_formatter! { CyrillicLower, LOWERCASE } |
| 33 | + |
| 34 | +/// Converts Cyrillic letters (e.g., "Б", "В", "БА") to their corresponding numeric values. |
| 35 | +/// The conversion follows Excel's alphabetic counter rules: 'А' = 1, 'Б' = 2, ..., |
| 36 | +/// 'Я' = 28, 'АА' = 29, etc. |
| 37 | +/// |
| 38 | +/// The `UPPERCASE` constant determines whether the string should be validated |
| 39 | +/// as uppercase or lowercase. |
| 40 | +/// |
| 41 | +/// # Returns |
| 42 | +/// |
| 43 | +/// Returns `Some(u32)` if conversion is successful; otherwise, returns `None`. |
| 44 | +#[inline] |
| 45 | +fn convert_letters_to_number<const UPPERCASE: bool>(value: &str) -> Option<u32> { |
| 46 | + if invalid_string::<UPPERCASE>(value) { |
| 47 | + return None; |
| 48 | + } |
| 49 | + let lookup = if UPPERCASE { &UPPERCASE_CYRILLIC } else { &LOWERCASE_CYRILLIC }; |
| 50 | + |
| 51 | + let result = value.chars().rev().enumerate().fold(0_u32, |acc, (i, c)| { |
| 52 | + if let Some(index) = lookup.iter().position(|&x| x == c) { |
| 53 | + acc + (index as u32 + 1) * 28_u32.pow(i as u32) |
| 54 | + } else { |
| 55 | + acc |
| 56 | + } |
| 57 | + }); |
| 58 | + Some(result) |
| 59 | +} |
| 60 | + |
| 61 | +/// Writes the numeric value as Cyrillic letters (e.g., 1 → "А", 28 → "Я") into the provided buffer. |
| 62 | +/// |
| 63 | +/// # Arguments |
| 64 | +/// |
| 65 | +/// * `num` - The numeric value to convert. |
| 66 | +/// * `width` - The minimum width of the generated string, padded with zeros if necessary. |
| 67 | +/// * `buf` - The buffer to write the resulting string into. |
| 68 | +#[inline] |
| 69 | +fn write_number_as_letters<const UPPERCASE: bool>( |
| 70 | + num: u32, |
| 71 | + width: usize, |
| 72 | + buf: &mut impl fmt::Write, |
| 73 | +) -> fmt::Result { |
| 74 | + let lookup = if UPPERCASE { &UPPERCASE_CYRILLIC } else { &LOWERCASE_CYRILLIC }; |
| 75 | + |
| 76 | + write_number_as_letters_gen(num, width, 28, |remainder| lookup[remainder as usize], buf) |
| 77 | +} |
| 78 | + |
| 79 | +/// Checks if a string is non-empty and consists only of valid uppercase or |
| 80 | +/// lowercase Cyrillic letters, excluding 'Ё', 'Й', 'Ъ', 'Ы', and 'Ь' |
| 81 | +/// ('ё', 'й', 'ъ', 'ы' and 'ь'). |
| 82 | +/// |
| 83 | +/// The `UPPERCASE` constant determines whether to check uppercase or lowercase letters. |
| 84 | +/// |
| 85 | +/// # Returns |
| 86 | +/// |
| 87 | +/// Returns `true` if the string is invalid; otherwise, returns `false`. |
| 88 | +#[inline] |
| 89 | +fn invalid_string<const UPPERCASE: bool>(str: &str) -> bool { |
| 90 | + if str.is_empty() { |
| 91 | + return true; |
| 92 | + } |
| 93 | + if UPPERCASE { |
| 94 | + !str.chars().all(|c| { |
| 95 | + // ('А'..='Я') == ('\u{0410}'..='\u{042F}') |
| 96 | + ('\u{0410}'..='\u{042F}').contains(&c) && !matches!(c, 'Ё' | 'Й' | 'Ъ' | 'Ы' | 'Ь') |
| 97 | + }) |
| 98 | + } else { |
| 99 | + !str.chars().all(|c| { |
| 100 | + // ('а'..='я') == ('\u{0430}'..='\u{044F}') |
| 101 | + ('\u{0430}'..='\u{044F}').contains(&c) && !matches!(c, 'ё' | 'й' | 'ъ' | 'ы' | 'ь') |
| 102 | + }) |
| 103 | + } |
| 104 | +} |
0 commit comments