lewp_css/domain/at_rules/media/
media_query.rs

1// This file is part of css. It is subject to the license terms in the COPYRIGHT file found in the top-level directory of this distribution and at https://raw.githubusercontent.com/lemonrock/css/master/COPYRIGHT. No part of predicator, including this file, may be copied, modified, propagated, or distributed except according to the terms contained in the COPYRIGHT file.
2// Copyright © 2017 The developers of css. See the COPYRIGHT file in the top-level directory of this distribution and at https://raw.githubusercontent.com/lemonrock/css/master/COPYRIGHT.
3
4use {
5    super::{MediaQueryType, Qualifier},
6    crate::{
7        domain::at_rules::media::MediaExpression,
8        parsers::{
9            separators::{Comma, Separated},
10            ParserContext,
11        },
12        CustomParseError,
13    },
14    cssparser::{ParseError, Parser, ToCss},
15    either::{Left, Right},
16    std::fmt::{self},
17};
18
19/// A [media query][mq].
20///
21/// [mq]: <https://drafts.csswg.org/mediaqueries/>
22#[derive(Clone, Debug, PartialEq)]
23pub struct MediaQuery {
24    /// The qualifier for this query.
25    pub qualifier: Option<Qualifier>,
26
27    /// The media type for this query, that can be known, unknown, or "all".
28    pub media_type: MediaQueryType,
29
30    /// The set of expressions that this media query contains.
31    pub expressions: Vec<MediaExpression>,
32}
33
34impl Separated for MediaQuery {
35    type Delimiter = Comma;
36}
37
38impl ToCss for MediaQuery {
39    fn to_css<W: fmt::Write>(&self, dest: &mut W) -> fmt::Result {
40        if let Some(qualifier) = self.qualifier {
41            qualifier.to_css(dest)?;
42            dest.write_char(' ')?;
43        }
44
45        use self::MediaQueryType::*;
46
47        match self.media_type {
48            All => {
49                // We need to print "all" if there's a qualifier, or there's just an empty list of expressions.
50                // Otherwise, we'd serialize media queries like "(min-width: 40px)" in "all (min-width: 40px)", which is unexpected.
51                if self.qualifier.is_some() || self.expressions.is_empty() {
52                    dest.write_str("all")?;
53                }
54            }
55
56            Concrete(ref value) => value.to_css(dest)?,
57        }
58
59        if self.expressions.is_empty() {
60            return Ok(());
61        }
62
63        if self.media_type != All || self.qualifier.is_some() {
64            dest.write_str(" and ")?;
65        }
66
67        self.expressions[0].to_css(dest)?;
68        for expression in self.expressions.iter().skip(1) {
69            dest.write_str(" and ")?;
70            expression.to_css(dest)?;
71        }
72
73        Ok(())
74    }
75}
76
77impl MediaQuery {
78    /// Return a media query that never matches, used for when we fail to parse a given media query.
79    #[inline(always)]
80    pub(crate) fn never_matching() -> Self {
81        Self {
82            qualifier: Some(Qualifier::Not),
83            media_type: MediaQueryType::All,
84            expressions: vec![],
85        }
86    }
87
88    /// Parse a media query given css input.
89    ///
90    /// Returns an error if any of the expressions is unknown.
91    pub(crate) fn parse<'i, 't>(
92        context: &ParserContext,
93        input: &mut Parser<'i, 't>,
94    ) -> Result<Self, ParseError<'i, CustomParseError<'i>>> {
95        let mut expressions = vec![];
96
97        use self::Qualifier::*;
98
99        let qualifier = if input
100            .r#try(|input| input.expect_ident_matching("only"))
101            .is_ok()
102        {
103            Some(Only)
104        } else if input
105            .r#try(|input| input.expect_ident_matching("not"))
106            .is_ok()
107        {
108            Some(Not)
109        } else {
110            None
111        };
112
113        let isThereAValidMediaType = input.r#try(|input| {
114            if let Ok(ident) = input.expect_ident() {
115                match MediaQueryType::parse(ident).map_err(ParseError::from) {
116                    Ok(mediaType) => Ok(Left(mediaType)),
117                    Err(error) => Ok(Right(error)),
118                }
119            } else {
120                Err(())
121            }
122        });
123
124        let media_type = match isThereAValidMediaType {
125            Ok(Left(media_type)) => media_type,
126
127            Ok(Right(error)) => return Err(error),
128
129            Err(()) => {
130                // Media type is only optional if qualifier is not specified.
131                if qualifier.is_some() {
132                    return Err(ParseError::from(
133                        CustomParseError::MediaTypeIsOnlyOptionalIfQualifiedIsNotSpecified,
134                    ));
135                }
136
137                // Without a media type, require at least one expression.
138                expressions.push(MediaExpression::parse(context, input)?);
139
140                MediaQueryType::All
141            }
142        };
143
144        // Parse any subsequent expressions
145        loop {
146            if input
147                .r#try(|input| input.expect_ident_matching("and"))
148                .is_err()
149            {
150                return Ok(Self {
151                    qualifier,
152                    media_type,
153                    expressions,
154                });
155            }
156            expressions.push(MediaExpression::parse(context, input)?)
157        }
158    }
159}