#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ListStyleType {
#[default]
Disc,
Circle,
Square,
None,
Decimal,
LowerAlpha,
UpperAlpha,
LowerRoman,
UpperRoman,
}
impl ListStyleType {
pub fn marker(self, ordinal: usize) -> String {
match self {
ListStyleType::Disc => "• ".to_string(),
ListStyleType::Circle => "○ ".to_string(),
ListStyleType::Square => "▪ ".to_string(),
ListStyleType::None => String::new(),
ListStyleType::Decimal => format!("{ordinal}. "),
ListStyleType::LowerAlpha => format!("{}. ", to_alpha(ordinal).to_ascii_lowercase()),
ListStyleType::UpperAlpha => format!("{}. ", to_alpha(ordinal)),
ListStyleType::LowerRoman => format!("{} ", to_roman(ordinal).to_ascii_lowercase()),
ListStyleType::UpperRoman => format!("{} ", to_roman(ordinal)),
}
}
}
fn to_alpha(mut n: usize) -> String {
let mut out = Vec::new();
while n > 0 {
n -= 1;
out.push(b'A' + (n % 26) as u8);
n /= 26;
}
out.reverse();
String::from_utf8(out).unwrap_or_default()
}
fn to_roman(mut n: usize) -> String {
const PAIRS: [(usize, &str); 13] = [
(1000, "M"),
(900, "CM"),
(500, "D"),
(400, "CD"),
(100, "C"),
(90, "XC"),
(50, "L"),
(40, "XL"),
(10, "X"),
(9, "IX"),
(5, "V"),
(4, "IV"),
(1, "I"),
];
let mut out = String::new();
for (value, glyph) in PAIRS {
while n >= value {
out.push_str(glyph);
n -= value;
}
}
out
}
pub fn parse_list_style_type(s: &str) -> Option<ListStyleType> {
match s.trim().to_ascii_lowercase().as_str() {
"disc" => Some(ListStyleType::Disc),
"circle" => Some(ListStyleType::Circle),
"square" => Some(ListStyleType::Square),
"none" => Some(ListStyleType::None),
"decimal" => Some(ListStyleType::Decimal),
"lower-alpha" | "lower-latin" => Some(ListStyleType::LowerAlpha),
"upper-alpha" | "upper-latin" => Some(ListStyleType::UpperAlpha),
"lower-roman" => Some(ListStyleType::LowerRoman),
"upper-roman" => Some(ListStyleType::UpperRoman),
_ => None,
}
}
pub fn is_list_style_type_keyword(word: &str) -> bool {
parse_list_style_type(word).is_some()
}