1use crate::{
15 css_types::{Angle, Color, Length, Percentage},
16 token_types::SimpleToken,
17};
18use std::fmt;
19
20#[derive(Debug, Clone, PartialEq)]
23pub enum CssValue {
24 Number(f64),
26
27 Percentage(f64),
29
30 Dimension {
32 value: f64,
33 unit: String,
34 },
35
36 String(String),
38
39 Ident(String),
41
42 Function {
44 name: String,
45 args: Vec<CssValue>,
46 },
47
48 Sequence(Vec<CssValue>),
50
51 Angle(Angle),
53 Color(Color),
54 Length(Length),
55
56 Token(SimpleToken),
58
59 None,
61}
62
63impl CssValue {
64 pub fn as_number(&self) -> Option<f64> {
65 match self {
66 CssValue::Number(n) => Some(*n),
67 CssValue::Percentage(p) => Some(*p),
68 CssValue::Dimension { value, .. } => Some(*value),
69 _ => None,
70 }
71 }
72
73 pub fn as_percentage(&self) -> Option<f64> {
75 match self {
76 CssValue::Percentage(p) => Some(*p),
77 _ => None,
78 }
79 }
80
81 pub fn as_string(&self) -> Option<&String> {
83 match self {
84 CssValue::String(s) | CssValue::Ident(s) => Some(s),
85 _ => None,
86 }
87 }
88
89 pub fn as_angle(&self) -> Option<&Angle> {
91 match self {
92 CssValue::Angle(a) => Some(a),
93 _ => None,
94 }
95 }
96
97 pub fn as_color(&self) -> Option<&Color> {
99 match self {
100 CssValue::Color(c) => Some(c),
101 _ => None,
102 }
103 }
104
105 pub fn as_dimension(&self) -> Option<(f64, &String)> {
107 match self {
108 CssValue::Dimension { value, unit } => Some((*value, unit)),
109 _ => None,
110 }
111 }
112
113 pub fn as_sequence(&self) -> Option<&Vec<CssValue>> {
115 match self {
116 CssValue::Sequence(seq) => Some(seq),
117 _ => None,
118 }
119 }
120
121 pub fn as_function(&self) -> Option<(&String, &Vec<CssValue>)> {
123 match self {
124 CssValue::Function { name, args } => Some((name, args)),
125 _ => None,
126 }
127 }
128
129 pub fn is_number(&self) -> bool {
130 matches!(self, CssValue::Number(_))
131 }
132
133 pub fn is_percentage(&self) -> bool {
134 matches!(self, CssValue::Percentage(_))
135 }
136
137 pub fn is_dimension(&self) -> bool {
138 matches!(self, CssValue::Dimension { .. })
139 }
140
141 pub fn is_string(&self) -> bool {
142 matches!(self, CssValue::String(_))
143 }
144
145 pub fn is_ident(&self) -> bool {
146 matches!(self, CssValue::Ident(_))
147 }
148
149 pub fn is_function(&self) -> bool {
150 matches!(self, CssValue::Function { .. })
151 }
152
153 pub fn is_sequence(&self) -> bool {
154 matches!(self, CssValue::Sequence(_))
155 }
156
157 pub fn is_angle(&self) -> bool {
158 matches!(self, CssValue::Angle(_))
159 }
160
161 pub fn is_color(&self) -> bool {
162 matches!(self, CssValue::Color(_))
163 }
164
165 pub fn is_none(&self) -> bool {
166 matches!(self, CssValue::None)
167 }
168
169 pub fn has_unit(&self, unit: &str) -> bool {
171 match self {
172 CssValue::Dimension { unit: u, .. } => u == unit,
173 _ => false,
174 }
175 }
176
177 pub fn get_unit(&self) -> Option<&String> {
179 match self {
180 CssValue::Dimension { unit, .. } => Some(unit),
181 _ => None,
182 }
183 }
184
185 pub fn number(value: f64) -> Self {
187 CssValue::Number(value)
188 }
189
190 pub fn percentage(value: f64) -> Self {
191 CssValue::Percentage(value)
192 }
193
194 pub fn dimension(value: f64, unit: impl Into<String>) -> Self {
195 CssValue::Dimension {
196 value,
197 unit: unit.into(),
198 }
199 }
200
201 pub fn string(value: impl Into<String>) -> Self {
202 CssValue::String(value.into())
203 }
204
205 pub fn ident(value: impl Into<String>) -> Self {
206 CssValue::Ident(value.into())
207 }
208
209 pub fn function(name: impl Into<String>, args: Vec<CssValue>) -> Self {
210 CssValue::Function {
211 name: name.into(),
212 args,
213 }
214 }
215
216 pub fn sequence(values: Vec<CssValue>) -> Self {
217 CssValue::Sequence(values)
218 }
219}
220
221impl fmt::Display for CssValue {
222 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
223 match self {
224 CssValue::Number(n) => write!(f, "{}", n),
225 CssValue::Percentage(p) => write!(f, "{}%", p),
226 CssValue::Dimension { value, unit } => write!(f, "{}{}", value, unit),
227 CssValue::String(s) => write!(f, "\"{}\"", s),
228 CssValue::Ident(s) => write!(f, "{}", s),
229 CssValue::Function { name, args } => {
230 write!(f, "{}(", name)?;
231 for (i, arg) in args.iter().enumerate() {
232 if i > 0 {
233 write!(f, ", ")?;
234 }
235 write!(f, "{}", arg)?;
236 }
237 write!(f, ")")
238 },
239 CssValue::Sequence(seq) => {
240 for (i, item) in seq.iter().enumerate() {
241 if i > 0 {
242 write!(f, " ")?;
243 }
244 write!(f, "{}", item)?;
245 }
246 Ok(())
247 },
248 CssValue::Angle(a) => write!(f, "{}", a),
249 CssValue::Color(c) => write!(f, "{}", c),
250 CssValue::Length(l) => write!(f, "{}", l),
251 CssValue::Token(t) => write!(f, "{:?}", t), CssValue::None => write!(f, "none"),
253 }
254 }
255}
256
257impl From<f64> for CssValue {
259 fn from(value: f64) -> Self {
260 CssValue::Number(value)
261 }
262}
263
264impl From<String> for CssValue {
265 fn from(value: String) -> Self {
266 CssValue::String(value)
267 }
268}
269
270impl From<&str> for CssValue {
271 fn from(value: &str) -> Self {
272 CssValue::String(value.to_string())
273 }
274}
275
276impl From<Angle> for CssValue {
277 fn from(value: Angle) -> Self {
278 CssValue::Angle(value)
279 }
280}
281
282impl From<Color> for CssValue {
283 fn from(value: Color) -> Self {
284 CssValue::Color(value)
285 }
286}
287
288impl From<Length> for CssValue {
289 fn from(value: Length) -> Self {
290 CssValue::Length(value)
291 }
292}
293
294impl From<Percentage> for CssValue {
295 fn from(value: Percentage) -> Self {
296 CssValue::Percentage(value.value as f64)
297 }
298}
299
300impl From<SimpleToken> for CssValue {
301 fn from(token: SimpleToken) -> Self {
302 match token {
303 SimpleToken::Number(n) => CssValue::Number(n),
304 SimpleToken::Percentage(p) => CssValue::Percentage(p),
305 SimpleToken::Dimension { value, unit } => CssValue::Dimension { value, unit },
306 SimpleToken::String(s) => CssValue::String(s),
307 SimpleToken::Ident(s) => CssValue::Ident(s),
308 _ => CssValue::Token(token),
309 }
310 }
311}
312
313#[cfg(test)]
314mod tests {
315 use super::*;
316
317 #[test]
318 fn test_css_value_creation() {
319 let num = CssValue::number(42.0);
320 let percent = CssValue::percentage(50.0);
321 let dim = CssValue::dimension(10.0, "px");
322 let str_val = CssValue::string("Arial");
323 let ident = CssValue::ident("auto");
324
325 assert_eq!(num.as_number(), Some(42.0));
326 assert_eq!(percent.as_percentage(), Some(50.0));
327 assert_eq!(dim.as_dimension(), Some((10.0, &"px".to_string())));
328 assert_eq!(str_val.as_string(), Some(&"Arial".to_string()));
329 assert_eq!(ident.as_string(), Some(&"auto".to_string()));
330 }
331
332 #[test]
333 fn test_type_checking() {
334 let num = CssValue::number(42.0);
335 let percent = CssValue::percentage(50.0);
336 let dim = CssValue::dimension(10.0, "px");
337
338 assert!(num.is_number());
339 assert!(!num.is_percentage());
340 assert!(!num.is_dimension());
341
342 assert!(percent.is_percentage());
343 assert!(!percent.is_number());
344
345 assert!(dim.is_dimension());
346 assert!(dim.has_unit("px"));
347 assert!(!dim.has_unit("em"));
348 }
349
350 #[test]
351 fn test_function_value() {
352 let func = CssValue::function(
353 "rgb",
354 vec![
355 CssValue::number(255.0),
356 CssValue::number(0.0),
357 CssValue::number(0.0),
358 ],
359 );
360
361 assert!(func.is_function());
362
363 if let Some((name, args)) = func.as_function() {
364 assert_eq!(name, "rgb");
365 assert_eq!(args.len(), 3);
366 assert_eq!(args[0].as_number(), Some(255.0));
367 }
368 }
369
370 #[test]
371 fn test_sequence_value() {
372 let seq = CssValue::sequence(vec![
373 CssValue::number(1.0),
374 CssValue::ident("solid"),
375 CssValue::ident("red"),
376 ]);
377
378 assert!(seq.is_sequence());
379
380 if let Some(items) = seq.as_sequence() {
381 assert_eq!(items.len(), 3);
382 assert!(items[0].is_number());
383 assert!(items[1].is_ident());
384 assert!(items[2].is_ident());
385 }
386 }
387
388 #[test]
389 fn test_display_formatting() {
390 let num = CssValue::number(42.0);
391 let percent = CssValue::percentage(50.0);
392 let dim = CssValue::dimension(10.0, "px");
393 let func = CssValue::function(
394 "calc",
395 vec![
396 CssValue::dimension(1.0, "px"),
397 CssValue::ident("+"),
398 CssValue::dimension(2.0, "em"),
399 ],
400 );
401
402 assert_eq!(num.to_string(), "42");
403 assert_eq!(percent.to_string(), "50%");
404 assert_eq!(dim.to_string(), "10px");
405 assert_eq!(func.to_string(), "calc(1px, +, 2em)");
406 }
407}