1use super::ident::Ident;
4use super::number::{CSSInteger, CSSNumber};
5use crate::error::{ParserError, PrinterError};
6use crate::printer::Printer;
7use crate::properties::custom::TokenList;
8use crate::stylesheet::ParserOptions;
9use crate::traits::{Parse, ToCss};
10use crate::values;
11#[cfg(feature = "visitor")]
12use crate::visitor::Visit;
13use cssparser::*;
14
15#[derive(Debug, PartialEq, Clone)]
18#[cfg_attr(
19 feature = "serde",
20 derive(serde::Serialize, serde::Deserialize),
21 serde(tag = "type", content = "value", rename_all = "kebab-case")
22)]
23#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
24#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
25pub enum SyntaxString {
26 Components(Vec<SyntaxComponent>),
28 Universal,
30}
31
32#[derive(Debug, PartialEq, Clone)]
38#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
39#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
40#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
41pub struct SyntaxComponent {
42 pub kind: SyntaxComponentKind,
44 pub multiplier: Multiplier,
46}
47
48#[derive(Debug, PartialEq, Clone)]
50#[cfg_attr(
51 feature = "serde",
52 derive(serde::Serialize, serde::Deserialize),
53 serde(tag = "type", content = "value", rename_all = "kebab-case")
54)]
55#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
56#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
57pub enum SyntaxComponentKind {
58 Length,
60 Number,
62 Percentage,
64 LengthPercentage,
66 Color,
68 Image,
70 Url,
72 Integer,
74 Angle,
76 Time,
78 Resolution,
80 TransformFunction,
82 TransformList,
84 CustomIdent,
86 Literal(String), }
89
90#[derive(Debug, PartialEq, Clone)]
93#[cfg_attr(feature = "visitor", derive(Visit))]
94#[cfg_attr(
95 feature = "serde",
96 derive(serde::Serialize, serde::Deserialize),
97 serde(tag = "type", content = "value", rename_all = "kebab-case")
98)]
99#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
100#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
101pub enum Multiplier {
102 None,
104 Space,
106 Comma,
108}
109
110#[derive(Debug, PartialEq, Clone)]
112#[cfg_attr(feature = "visitor", derive(Visit))]
113#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
114#[cfg_attr(
115 feature = "serde",
116 derive(serde::Serialize, serde::Deserialize),
117 serde(tag = "type", content = "value", rename_all = "kebab-case")
118)]
119#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
120pub enum ParsedComponent<'i> {
121 Length(values::length::Length),
123 Number(CSSNumber),
125 Percentage(values::percentage::Percentage),
127 LengthPercentage(values::length::LengthPercentage),
129 Color(values::color::CssColor),
131 #[cfg_attr(feature = "serde", serde(borrow))]
133 Image(values::image::Image<'i>),
134 Url(values::url::Url<'i>),
136 Integer(CSSInteger),
138 Angle(values::angle::Angle),
140 Time(values::time::Time),
142 Resolution(values::resolution::Resolution),
144 TransformFunction(crate::properties::transform::Transform),
146 TransformList(crate::properties::transform::TransformList),
148 CustomIdent(values::ident::CustomIdent<'i>),
150 Literal(Ident<'i>),
152 Repeated {
154 #[cfg_attr(feature = "visitor", skip_type)]
156 components: Vec<ParsedComponent<'i>>,
157 multiplier: Multiplier,
159 },
160 TokenList(crate::properties::custom::TokenList<'i>),
162}
163
164impl<'i> SyntaxString {
165 pub fn parse_string(input: &'i str) -> Result<SyntaxString, ()> {
167 let mut input = input.trim_matches(SPACE_CHARACTERS);
169 if input.is_empty() {
170 return Err(());
171 }
172
173 if input == "*" {
174 return Ok(SyntaxString::Universal);
175 }
176
177 let mut components = Vec::new();
178 loop {
179 let component = SyntaxComponent::parse_string(&mut input)?;
180 components.push(component);
181
182 input = input.trim_start_matches(SPACE_CHARACTERS);
183 if input.is_empty() {
184 break;
185 }
186
187 if input.starts_with('|') {
188 input = &input[1..];
189 continue;
190 }
191
192 return Err(());
193 }
194
195 Ok(SyntaxString::Components(components))
196 }
197
198 pub fn parse_value<'t>(
200 &self,
201 input: &mut Parser<'i, 't>,
202 ) -> Result<ParsedComponent<'i>, ParseError<'i, ParserError<'i>>> {
203 match self {
204 SyntaxString::Universal => Ok(ParsedComponent::TokenList(TokenList::parse(
205 input,
206 &ParserOptions::default(),
207 0,
208 )?)),
209 SyntaxString::Components(components) => {
210 for component in components {
212 let state = input.state();
213 let mut parsed = Vec::new();
214 loop {
215 let value: Result<ParsedComponent<'i>, ParseError<'i, ParserError<'i>>> = input.try_parse(|input| {
216 Ok(match &component.kind {
217 SyntaxComponentKind::Length => ParsedComponent::Length(values::length::Length::parse(input)?),
218 SyntaxComponentKind::Number => ParsedComponent::Number(CSSNumber::parse(input)?),
219 SyntaxComponentKind::Percentage => {
220 ParsedComponent::Percentage(values::percentage::Percentage::parse(input)?)
221 }
222 SyntaxComponentKind::LengthPercentage => {
223 ParsedComponent::LengthPercentage(values::length::LengthPercentage::parse(input)?)
224 }
225 SyntaxComponentKind::Color => ParsedComponent::Color(values::color::CssColor::parse(input)?),
226 SyntaxComponentKind::Image => ParsedComponent::Image(values::image::Image::parse(input)?),
227 SyntaxComponentKind::Url => ParsedComponent::Url(values::url::Url::parse(input)?),
228 SyntaxComponentKind::Integer => ParsedComponent::Integer(CSSInteger::parse(input)?),
229 SyntaxComponentKind::Angle => ParsedComponent::Angle(values::angle::Angle::parse(input)?),
230 SyntaxComponentKind::Time => ParsedComponent::Time(values::time::Time::parse(input)?),
231 SyntaxComponentKind::Resolution => {
232 ParsedComponent::Resolution(values::resolution::Resolution::parse(input)?)
233 }
234 SyntaxComponentKind::TransformFunction => {
235 ParsedComponent::TransformFunction(crate::properties::transform::Transform::parse(input)?)
236 }
237 SyntaxComponentKind::TransformList => {
238 ParsedComponent::TransformList(crate::properties::transform::TransformList::parse(input)?)
239 }
240 SyntaxComponentKind::CustomIdent => {
241 ParsedComponent::CustomIdent(values::ident::CustomIdent::parse(input)?)
242 }
243 SyntaxComponentKind::Literal(value) => {
244 let location = input.current_source_location();
245 let ident = input.expect_ident()?;
246 if *ident != &value {
247 return Err(location.new_unexpected_token_error(Token::Ident(ident.clone())));
248 }
249 ParsedComponent::Literal(ident.into())
250 }
251 })
252 });
253
254 if let Ok(value) = value {
255 match component.multiplier {
256 Multiplier::None => return Ok(value),
257 Multiplier::Space => {
258 parsed.push(value);
259 if input.is_exhausted() {
260 return Ok(ParsedComponent::Repeated {
261 components: parsed,
262 multiplier: component.multiplier.clone(),
263 });
264 }
265 }
266 Multiplier::Comma => {
267 parsed.push(value);
268 match input.next() {
269 Err(_) => {
270 return Ok(ParsedComponent::Repeated {
271 components: parsed,
272 multiplier: component.multiplier.clone(),
273 })
274 }
275 Ok(&Token::Comma) => continue,
276 Ok(_) => break,
277 }
278 }
279 }
280 } else {
281 break;
282 }
283 }
284
285 input.reset(&state);
286 }
287
288 Err(input.new_error_for_next_token())
289 }
290 }
291 }
292
293 pub fn parse_value_from_string<'t>(
295 &self,
296 input: &'i str,
297 ) -> Result<ParsedComponent<'i>, ParseError<'i, ParserError<'i>>> {
298 let mut input = ParserInput::new(input);
299 let mut parser = Parser::new(&mut input);
300 self.parse_value(&mut parser)
301 }
302}
303
304impl SyntaxComponent {
305 fn parse_string(input: &mut &str) -> Result<SyntaxComponent, ()> {
306 let kind = SyntaxComponentKind::parse_string(input)?;
307
308 if kind == SyntaxComponentKind::TransformList {
310 return Ok(SyntaxComponent {
311 kind,
312 multiplier: Multiplier::None,
313 });
314 }
315
316 let multiplier = if input.starts_with('+') {
317 *input = &input[1..];
318 Multiplier::Space
319 } else if input.starts_with('#') {
320 *input = &input[1..];
321 Multiplier::Comma
322 } else {
323 Multiplier::None
324 };
325
326 Ok(SyntaxComponent { kind, multiplier })
327 }
328}
329
330static SPACE_CHARACTERS: &'static [char] = &['\u{0020}', '\u{0009}'];
332
333impl SyntaxComponentKind {
334 fn parse_string(input: &mut &str) -> Result<SyntaxComponentKind, ()> {
335 *input = input.trim_start_matches(SPACE_CHARACTERS);
337 if input.starts_with('<') {
338 let end_idx = input.find('>').ok_or(())?;
340 let name = &input[1..end_idx];
341 let component = match_ignore_ascii_case! {name,
342 "length" => SyntaxComponentKind::Length,
343 "number" => SyntaxComponentKind::Number,
344 "percentage" => SyntaxComponentKind::Percentage,
345 "length-percentage" => SyntaxComponentKind::LengthPercentage,
346 "color" => SyntaxComponentKind::Color,
347 "image" => SyntaxComponentKind::Image,
348 "url" => SyntaxComponentKind::Url,
349 "integer" => SyntaxComponentKind::Integer,
350 "angle" => SyntaxComponentKind::Angle,
351 "time" => SyntaxComponentKind::Time,
352 "resolution" => SyntaxComponentKind::Resolution,
353 "transform-function" => SyntaxComponentKind::TransformFunction,
354 "transform-list" => SyntaxComponentKind::TransformList,
355 "custom-ident" => SyntaxComponentKind::CustomIdent,
356 _ => return Err(())
357 };
358
359 *input = &input[end_idx + 1..];
360 Ok(component)
361 } else if input.starts_with(is_ident_start) {
362 let end_idx = input.find(|c| !is_name_code_point(c)).unwrap_or_else(|| input.len());
364 let name = input[0..end_idx].to_owned();
365 *input = &input[end_idx..];
366 Ok(SyntaxComponentKind::Literal(name))
367 } else {
368 return Err(());
369 }
370 }
371}
372
373#[inline]
374fn is_ident_start(c: char) -> bool {
375 c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z' || c >= '\u{80}' || c == '_'
377}
378
379#[inline]
380fn is_name_code_point(c: char) -> bool {
381 is_ident_start(c) || c >= '0' && c <= '9' || c == '-'
383}
384
385impl<'i> Parse<'i> for SyntaxString {
386 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
387 let string = input.expect_string_cloned()?;
388 SyntaxString::parse_string(string.as_ref()).map_err(|_| input.new_custom_error(ParserError::InvalidValue))
389 }
390}
391
392impl ToCss for SyntaxString {
393 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
394 where
395 W: std::fmt::Write,
396 {
397 dest.write_char('"')?;
398 match self {
399 SyntaxString::Universal => dest.write_char('*')?,
400 SyntaxString::Components(components) => {
401 let mut first = true;
402 for component in components {
403 if first {
404 first = false;
405 } else {
406 dest.delim('|', true)?;
407 }
408
409 component.to_css(dest)?;
410 }
411 }
412 }
413
414 dest.write_char('"')
415 }
416}
417
418impl ToCss for SyntaxComponent {
419 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
420 where
421 W: std::fmt::Write,
422 {
423 self.kind.to_css(dest)?;
424 match self.multiplier {
425 Multiplier::None => Ok(()),
426 Multiplier::Comma => dest.write_char('#'),
427 Multiplier::Space => dest.write_char('+'),
428 }
429 }
430}
431
432impl ToCss for SyntaxComponentKind {
433 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
434 where
435 W: std::fmt::Write,
436 {
437 use SyntaxComponentKind::*;
438 let s = match self {
439 Length => "<length>",
440 Number => "<number>",
441 Percentage => "<percentage>",
442 LengthPercentage => "<length-percentage>",
443 Color => "<color>",
444 Image => "<image>",
445 Url => "<url>",
446 Integer => "<integer>",
447 Angle => "<angle>",
448 Time => "<time>",
449 Resolution => "<resolution>",
450 TransformFunction => "<transform-function>",
451 TransformList => "<transform-list>",
452 CustomIdent => "<custom-ident>",
453 Literal(l) => l,
454 };
455 dest.write_str(s)
456 }
457}
458
459impl<'i> ToCss for ParsedComponent<'i> {
460 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
461 where
462 W: std::fmt::Write,
463 {
464 use ParsedComponent::*;
465 match self {
466 Length(v) => v.to_css(dest),
467 Number(v) => v.to_css(dest),
468 Percentage(v) => v.to_css(dest),
469 LengthPercentage(v) => v.to_css(dest),
470 Color(v) => v.to_css(dest),
471 Image(v) => v.to_css(dest),
472 Url(v) => v.to_css(dest),
473 Integer(v) => v.to_css(dest),
474 Angle(v) => v.to_css(dest),
475 Time(v) => v.to_css(dest),
476 Resolution(v) => v.to_css(dest),
477 TransformFunction(v) => v.to_css(dest),
478 TransformList(v) => v.to_css(dest),
479 CustomIdent(v) => v.to_css(dest),
480 Literal(v) => v.to_css(dest),
481 Repeated { components, multiplier } => {
482 let mut first = true;
483 for component in components {
484 if first {
485 first = false;
486 } else {
487 match multiplier {
488 Multiplier::Comma => dest.delim(',', false)?,
489 Multiplier::Space => dest.write_char(' ')?,
490 Multiplier::None => unreachable!(),
491 }
492 }
493
494 component.to_css(dest)?;
495 }
496 Ok(())
497 }
498 TokenList(t) => t.to_css(dest, false),
499 }
500 }
501}
502
503#[cfg(test)]
504mod tests {
505 use crate::values::color::RGBA;
506
507 use super::*;
508
509 fn test(source: &str, test: &str, expected: ParsedComponent) {
510 let parsed = SyntaxString::parse_string(source).unwrap();
511
512 let mut input = ParserInput::new(test);
513 let mut parser = Parser::new(&mut input);
514 let value = parsed.parse_value(&mut parser).unwrap();
515 assert_eq!(value, expected);
516 }
517
518 fn parse_error_test(source: &str) {
519 let res = SyntaxString::parse_string(source);
520 match res {
521 Ok(_) => unreachable!(),
522 Err(_) => {}
523 }
524 }
525
526 fn error_test(source: &str, test: &str) {
527 let parsed = SyntaxString::parse_string(source).unwrap();
528 let mut input = ParserInput::new(test);
529 let mut parser = Parser::new(&mut input);
530 let res = parsed.parse_value(&mut parser);
531 match res {
532 Ok(_) => unreachable!(),
533 Err(_) => {}
534 }
535 }
536
537 #[test]
538 fn test_syntax() {
539 test(
540 "foo | <color>+ | <integer>",
541 "foo",
542 ParsedComponent::Literal("foo".into()),
543 );
544
545 test("foo|<color>+|<integer>", "foo", ParsedComponent::Literal("foo".into()));
546
547 test("foo | <color>+ | <integer>", "2", ParsedComponent::Integer(2));
548
549 test(
550 "foo | <color>+ | <integer>",
551 "red",
552 ParsedComponent::Repeated {
553 components: vec![ParsedComponent::Color(values::color::CssColor::RGBA(RGBA {
554 red: 255,
555 green: 0,
556 blue: 0,
557 alpha: 255,
558 }))],
559 multiplier: Multiplier::Space,
560 },
561 );
562
563 test(
564 "foo | <color>+ | <integer>",
565 "red blue",
566 ParsedComponent::Repeated {
567 components: vec![
568 ParsedComponent::Color(values::color::CssColor::RGBA(RGBA {
569 red: 255,
570 green: 0,
571 blue: 0,
572 alpha: 255,
573 })),
574 ParsedComponent::Color(values::color::CssColor::RGBA(RGBA {
575 red: 0,
576 green: 0,
577 blue: 255,
578 alpha: 255,
579 })),
580 ],
581 multiplier: Multiplier::Space,
582 },
583 );
584
585 error_test("foo | <color>+ | <integer>", "2.5");
586
587 error_test("foo | <color>+ | <integer>", "25px");
588
589 error_test("foo | <color>+ | <integer>", "red, green");
590
591 test(
592 "foo | <color># | <integer>",
593 "red, blue",
594 ParsedComponent::Repeated {
595 components: vec![
596 ParsedComponent::Color(values::color::CssColor::RGBA(RGBA {
597 red: 255,
598 green: 0,
599 blue: 0,
600 alpha: 255,
601 })),
602 ParsedComponent::Color(values::color::CssColor::RGBA(RGBA {
603 red: 0,
604 green: 0,
605 blue: 255,
606 alpha: 255,
607 })),
608 ],
609 multiplier: Multiplier::Comma,
610 },
611 );
612
613 error_test("foo | <color># | <integer>", "red green");
614
615 test(
616 "<length>",
617 "25px",
618 ParsedComponent::Length(values::length::Length::Value(values::length::LengthValue::Px(25.0))),
619 );
620
621 test(
622 "<length>",
623 "calc(25px + 25px)",
624 ParsedComponent::Length(values::length::Length::Value(values::length::LengthValue::Px(50.0))),
625 );
626
627 test(
628 "<length> | <percentage>",
629 "25px",
630 ParsedComponent::Length(values::length::Length::Value(values::length::LengthValue::Px(25.0))),
631 );
632
633 test(
634 "<length> | <percentage>",
635 "25%",
636 ParsedComponent::Percentage(values::percentage::Percentage(0.25)),
637 );
638
639 error_test("<length> | <percentage>", "calc(100% - 25px)");
640
641 test("foo | bar | baz", "bar", ParsedComponent::Literal("bar".into()));
642
643 test(
644 "<custom-ident>",
645 "hi",
646 ParsedComponent::CustomIdent(values::ident::CustomIdent("hi".into())),
647 );
648
649 parse_error_test("<transform-list>#");
650 parse_error_test("<color");
651 parse_error_test("color>");
652 }
653}