Parse CSS HSL

This commit is contained in:
Adrian Wozniak 2020-04-27 20:25:25 +02:00
parent b706da069e
commit 3be8fc7103
3 changed files with 140 additions and 77 deletions

View File

@ -1,5 +1,85 @@
use std::str::FromStr;
/// rgba(255, 255, 255, 1.0)
/// 012345
/// rgb(255, 255, 255, 1.0)
/// 01234
pub fn parse_rgba(s: &str, parse_alpha: bool) -> Result<(u8, u8, u8, u8), String> {
let start_idx = if parse_alpha { 5 } else { 4 };
let v: Vec<String> = s[start_idx..(s.len() - 1)]
.split(',')
.map(|s| s.to_string())
.collect();
let r = v
.get(0)
.ok_or_else(|| format!("invalid color {:?}", s))
.and_then(|s| s.parse().map_err(|_| format!("invalid color {:?}", s)))?;
let g = v
.get(1)
.ok_or_else(|| format!("invalid color {:?}", s))
.and_then(|s| s.parse().map_err(|_| format!("invalid color {:?}", s)))?;
let b = v
.get(2)
.ok_or_else(|| format!("invalid color {:?}", s))
.and_then(|s| s.parse().map_err(|_| format!("invalid color {:?}", s)))?;
let a = if parse_alpha {
(v.get(3)
.ok_or_else(|| format!("invalid color {:?}", s))
.and_then(|s| {
s.parse::<f64>()
.map_err(|_| format!("invalid color {:?}", s))
})?
* 255f64) as u8
} else {
255
};
Ok((r, g, b, a))
}
/// hsla(360, 100%, 100%, 1.0)
/// 012345
/// hsl(360, 100%, 100%, 1.0)
/// 01234
pub fn parse_hsla(given: &str, parse_alpha: bool) -> Result<(u16, u8, u8, f64), String> {
let start_idx = if parse_alpha { 5 } else { 4 };
let v: Vec<String> = given[start_idx..(given.len() - 1)]
.split(',')
.map(|s| s.to_string())
.collect();
let h = v
.get(0)
.ok_or_else(|| format!("invalid color {:?}", given))
.and_then(|s| s.parse().map_err(|_| format!("invalid color {:?}", given)))?;
let s = parse_percent(given, v.get(1))?;
let l = parse_percent(given, v.get(2))?;
let a = if parse_alpha {
v.get(3)
.ok_or_else(|| format!("invalid color {:?}", given))
.and_then(|s| {
s.parse::<f64>()
.map_err(|_| format!("invalid color {:?}", given))
})?
} else {
1.0f64
};
Ok((h, s, l, a))
}
fn parse_percent(s: &str, v: Option<&String>) -> Result<u8, String> {
v.ok_or_else(|| format!("invalid color {:?}", s))
.and_then(|s| {
if s.ends_with('%') {
Ok(s[0..(s.len() - 1)].to_string())
} else {
Err(format!("invalid color {:?}", s))
}
})
.and_then(|s| {
s.parse::<u8>()
.map_err(|_| format!("invalid color {:?}", s))
})
}
pub enum Color {
AliceBlue,
AntiqueWhite,

View File

@ -12,7 +12,7 @@ const INPUT: &str = "./jirs-client/js/styles.css";
type Css = Arc<RwLock<CssFile>>;
mod predefined;
mod colors;
mod prop;
#[derive(Debug)]

View File

@ -2,6 +2,8 @@ use std::iter::Peekable;
use std::str::{Chars, FromStr};
use std::vec::IntoIter;
use crate::colors::{parse_hsla, parse_rgba};
#[derive(Debug)]
pub struct CssTokenizer<'l> {
it: Peekable<Chars<'l>>,
@ -59,10 +61,24 @@ impl<'l> CssTokenizer<'l> {
}
}
#[derive(Debug)]
pub struct ParserPosition {
line: usize,
line_character: usize,
character: usize,
}
impl std::fmt::Display for ParserPosition {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(format!("({}:{}:{})", self.line, self.line_character, self.character).as_str())
}
}
#[derive(Debug)]
pub struct CssParser {
it: Peekable<IntoIter<String>>,
selectors: Vec<Selector>,
pos: ParserPosition,
}
impl CssParser {
@ -70,6 +86,11 @@ impl CssParser {
Self {
it: tokens.into_iter().peekable(),
selectors: vec![],
pos: ParserPosition {
line: 0,
line_character: 0,
character: 0,
},
}
}
@ -526,7 +547,21 @@ impl CssParser {
}
fn consume(&mut self) -> Option<String> {
self.it.next()
let current = self.it.next();
if let Some(s) = current.as_ref() {
match s.as_str() {
"\n" => {
self.pos.character += s.len();
self.pos.line += 1;
self.pos.line_character += 0;
}
_ => {
self.pos.character += s.len();
self.pos.line_character += s.len();
}
}
}
current
}
fn expect_consume(&mut self) -> Result<String, String> {
@ -1111,8 +1146,8 @@ impl FromStr for ZIndexProperty {
#[derive(Debug, PartialEq)]
pub enum ColorProperty {
Name(String),
Rgba(u16, u16, u16, u16),
Hsla(u16, u16, u16, u16),
Rgba(u8, u8, u8, u8),
Hsla(u16, u8, u8, f64),
Current,
}
@ -1120,99 +1155,48 @@ impl FromStr for ColorProperty {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let p = match s.trim().to_lowercase().as_str() {
"currentcolor" => ColorProperty::Current,
let p = match s.trim() {
"currentColor" => ColorProperty::Current,
_ if s.len() == 7 && s.starts_with('#') => {
let (r, g, b) = (
u16::from_str_radix(&s[1..2], 16)
u8::from_str_radix(&s[1..2], 16)
.map_err(|_| format!("invalid color {:?}", s))?,
u16::from_str_radix(&s[3..4], 16)
u8::from_str_radix(&s[3..4], 16)
.map_err(|_| format!("invalid color {:?}", s))?,
u16::from_str_radix(&s[5..6], 16)
u8::from_str_radix(&s[5..6], 16)
.map_err(|_| format!("invalid color {:?}", s))?,
);
ColorProperty::Rgba(r, g, b, 255)
}
_ if s.len() == 9 && s.starts_with('#') => {
let (r, g, b, a) = (
u16::from_str_radix(&s[1..2], 16)
u8::from_str_radix(&s[1..2], 16)
.map_err(|_| format!("invalid color {:?}", s))?,
u16::from_str_radix(&s[3..4], 16)
u8::from_str_radix(&s[3..4], 16)
.map_err(|_| format!("invalid color {:?}", s))?,
u16::from_str_radix(&s[5..6], 16)
u8::from_str_radix(&s[5..6], 16)
.map_err(|_| format!("invalid color {:?}", s))?,
u16::from_str_radix(&s[7..8], 16)
u8::from_str_radix(&s[7..8], 16)
.map_err(|_| format!("invalid color {:?}", s))?,
);
ColorProperty::Rgba(r, g, b, a)
}
_ if s.starts_with("rgba(") => {
let v: Vec<String> = s[5..(s.len() - 1)]
.split(',')
.map(|s| s.to_string())
.collect();
let r = v
.get(0)
.ok_or_else(|| format!("invalid color {:?}", s))
.and_then(|s| {
s.parse::<u16>()
.map_err(|_| format!("invalid color {:?}", s))
})?;
let g = v
.get(1)
.ok_or_else(|| format!("invalid color {:?}", s))
.and_then(|s| {
s.parse::<u16>()
.map_err(|_| format!("invalid color {:?}", s))
})?;
let b = v
.get(2)
.ok_or_else(|| format!("invalid color {:?}", s))
.and_then(|s| {
s.parse::<u16>()
.map_err(|_| format!("invalid color {:?}", s))
})?;
let a = (v
.get(3)
.ok_or_else(|| format!("invalid color {:?}", s))
.and_then(|s| {
s.parse::<f64>()
.map_err(|_| format!("invalid color {:?}", s))
})?
* 255f64) as u16;
_ if s.trim().to_lowercase().starts_with("rgba(") => {
let (r, g, b, a) = parse_rgba(s.trim(), true)?;
ColorProperty::Rgba(r, g, b, a)
}
_ if s.starts_with("rgb(") => {
let v: Vec<String> = s[5..(s.len() - 1)]
.split(',')
.map(|s| s.to_string())
.collect();
let r = v
.get(0)
.ok_or_else(|| format!("invalid color {:?}", s))
.and_then(|s| {
s.parse::<u16>()
.map_err(|_| format!("invalid color {:?}", s))
})?;
let g = v
.get(1)
.ok_or_else(|| format!("invalid color {:?}", s))
.and_then(|s| {
s.parse::<u16>()
.map_err(|_| format!("invalid color {:?}", s))
})?;
let b = v
.get(2)
.ok_or_else(|| format!("invalid color {:?}", s))
.and_then(|s| {
s.parse::<u16>()
.map_err(|_| format!("invalid color {:?}", s))
})?;
let a = 255;
_ if s.trim().to_lowercase().starts_with("rgb(") => {
let (r, g, b, a) = parse_rgba(s.trim(), false)?;
ColorProperty::Rgba(r, g, b, a)
}
// _ if s.starts_with("hsla(") => {}
// _ if s.starts_with("hsl(") => {}
_ if s.trim().to_lowercase().starts_with("hsla(") => {
let (h, s, l, a) = parse_hsla(s.trim(), true)?;
ColorProperty::Hsla(h, s, l, a)
}
_ if s.trim().to_lowercase().starts_with("hsl(") => {
let (h, s, l, a) = parse_hsla(s.trim(), false)?;
ColorProperty::Hsla(h, s, l, a)
}
_ => return Err(format!("invalid color {:?}", s)),
};
Ok(p)
@ -1439,7 +1423,6 @@ pub enum Property {
#[cfg(test)]
mod tests {
use crate::prop::Property::AnimationDelay;
use crate::prop::*;
#[test]