use std::error::Error;
use std::fmt;
#[derive(Debug, PartialEq, Copy, Clone)]
pub(crate) enum CSSNumeric {
Integer(isize),
Float(f64),
Percentage(isize),
}
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
#[allow(clippy::enum_variant_names)]
pub enum CSSParseError {
InvalidNumericCharacters,
InvalidNumericSyntax,
InvalidColorSyntax,
}
impl fmt::Display for CSSParseError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "CSS parsing error")
}
}
impl Error for CSSParseError {
fn description(&self) -> &str {
match *self {
CSSParseError::InvalidNumericCharacters => "Unexpected non-numeric characters",
CSSParseError::InvalidNumericSyntax => "Invalid numeric syntax",
CSSParseError::InvalidColorSyntax => "Invalid color syntax",
}
}
}
fn parse_css_integer(num: &str) -> u64 {
num.parse().unwrap()
}
fn parse_css_float(num: &str) -> f64 {
num.parse().unwrap()
}
pub(crate) fn parse_css_number(num: &str) -> Result<CSSNumeric, CSSParseError> {
let mut chars: Vec<char> = num.chars().collect();
if !chars.iter().all(|&c| "0123456789-+.%".contains(c)) {
return Err(CSSParseError::InvalidNumericCharacters);
}
let is_positive = match chars[0] {
'-' => false,
'+' => true,
_ => true,
};
if "-+".contains(chars[0]) {
chars.remove(0);
}
if chars.is_empty() {
return Err(CSSParseError::InvalidNumericSyntax);
}
if chars.iter().any(|&c| "-=".contains(c)) {
return Err(CSSParseError::InvalidNumericSyntax);
}
match chars.iter().filter(|&c| c == &'.').count() {
0 => {
match chars.iter().filter(|&c| c == &'%').count() {
0 => {
let uint = parse_css_integer(&(chars.iter().collect::<String>()));
let int = if is_positive {
uint as isize
} else {
-(uint as isize)
};
Ok(CSSNumeric::Integer(int))
}
1 => {
if chars.iter().last().unwrap() == &'%' {
chars.pop();
let uint = parse_css_integer(&(chars.iter().collect::<String>()));
let int = if is_positive {
uint as isize
} else {
-(uint as isize)
};
Ok(CSSNumeric::Percentage(int))
} else {
Err(CSSParseError::InvalidNumericSyntax)
}
}
_ => {
Err(CSSParseError::InvalidNumericSyntax)
}
}
}
1 => {
let ufloat = parse_css_float(&(chars.iter().collect::<String>()));
let float = if is_positive { ufloat } else { -ufloat };
Ok(CSSNumeric::Float(float))
}
_ => {
Err(CSSParseError::InvalidNumericSyntax)
}
}
}
#[cfg(test)]
mod tests {
#[allow(unused_imports)]
use super::*;
#[test]
fn test_css_parse_integer() {
let num1 = match parse_css_number("184").unwrap() {
CSSNumeric::Integer(val) => val,
_ => 0,
};
assert_eq!(num1, 184);
let num2 = match parse_css_number("00423").unwrap() {
CSSNumeric::Integer(val) => val,
_ => 0,
};
assert_eq!(num2, 423);
let num3 = match parse_css_number("-00423").unwrap() {
CSSNumeric::Integer(val) => val,
_ => 0,
};
assert_eq!(num3, -423);
let num4 = match parse_css_number("+00423").unwrap() {
CSSNumeric::Integer(val) => val,
_ => 0,
};
assert_eq!(num4, 423);
}
#[test]
fn test_css_parse_float() {
let num1 = match parse_css_number("0.37").unwrap() {
CSSNumeric::Float(val) => val,
_ => 0.,
};
assert_eq!(format!("{}", num1), "0.37");
let num2 = match parse_css_number(".423").unwrap() {
CSSNumeric::Float(val) => val,
_ => 0.,
};
assert_eq!(format!("{}", num2), "0.423");
let num3 = match parse_css_number("-00.423").unwrap() {
CSSNumeric::Float(val) => val,
_ => 0.,
};
assert_eq!(format!("{}", num3), "-0.423");
let num4 = match parse_css_number("+00.423").unwrap() {
CSSNumeric::Float(val) => val,
_ => 0.,
};
assert_eq!(format!("{}", num4), "0.423");
}
#[test]
fn test_css_parse_percentages() {
let num1 = match parse_css_number("184%").unwrap() {
CSSNumeric::Percentage(val) => val,
_ => 0,
};
assert_eq!(num1, 184);
let num2 = match parse_css_number("00423%").unwrap() {
CSSNumeric::Percentage(val) => val,
_ => 0,
};
assert_eq!(num2, 423);
let num3 = match parse_css_number("-00423%").unwrap() {
CSSNumeric::Percentage(val) => val,
_ => 0,
};
assert_eq!(num3, -423);
let num4 = match parse_css_number("+00423%").unwrap() {
CSSNumeric::Percentage(val) => val,
_ => 0,
};
assert_eq!(num4, 423);
}
#[test]
fn test_errors() {
assert_eq!(
parse_css_number("abc"),
Err(CSSParseError::InvalidNumericCharacters)
);
assert_eq!(
parse_css_number("14.23.2"),
Err(CSSParseError::InvalidNumericSyntax)
);
assert_eq!(
parse_css_number("-24%%"),
Err(CSSParseError::InvalidNumericSyntax)
);
assert_eq!(
parse_css_number("1%2%"),
Err(CSSParseError::InvalidNumericSyntax)
);
}
}