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