deft_simplecss/
lib.rs

1// Copyright 2016 the SimpleCSS Authors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4/*!
5A simple [CSS 2.1](https://www.w3.org/TR/CSS21/) parser and selector.
6
7This is not a browser-grade CSS parser. If you need one,
8use [cssparser](https://crates.io/crates/cssparser) +
9[selectors](https://crates.io/crates/selectors).
10
11Since it's very simple we will start with limitations:
12
13## Limitations
14
15- [At-rules](https://www.w3.org/TR/CSS21/syndata.html#at-rules) are not supported.
16  They will be skipped during parsing.
17- Property values are not parsed.
18  In CSS like `* { width: 5px }` you will get a `width` property with a `5px` value as a string.
19- CDO/CDC comments are not supported.
20- Parser is case sensitive. All keywords must be lowercase.
21- Unicode escape, like `\26`, is not supported.
22
23## Features
24
25- Selector matching support.
26- The rules are sorted by specificity.
27- `!important` parsing support.
28- Has a high-level parsers and low-level, zero-allocation tokenizers.
29- No unsafe.
30*/
31
32// LINEBENDER LINT SET - lib.rs - v2
33// See https://linebender.org/wiki/canonical-lints/
34// These lints aren't included in Cargo.toml because they
35// shouldn't apply to examples and tests
36#![warn(unused_crate_dependencies)]
37#![warn(clippy::print_stdout, clippy::print_stderr)]
38// Targeting e.g. 32-bit means structs containing usize can give false positives for 64-bit.
39#![cfg_attr(target_pointer_width = "64", warn(clippy::trivially_copy_pass_by_ref))]
40// END LINEBENDER LINT SET
41#![cfg_attr(docsrs, feature(doc_auto_cfg))]
42#![no_std]
43// The following lints are part of the Linebender standard set,
44// but resolving them has been deferred for now.
45// Feel free to send a PR that solves one or more of these.
46#![allow(
47    missing_debug_implementations,
48    unreachable_pub,
49    clippy::use_self,
50    clippy::missing_assert_message,
51    clippy::missing_panics_doc,
52    clippy::exhaustive_enums,
53    clippy::unseparated_literal_suffix
54)]
55#![cfg_attr(test, allow(unused_crate_dependencies))] // Some dev dependencies are only used in tests
56
57extern crate alloc;
58#[cfg(feature = "std")]
59extern crate std;
60
61use alloc::vec::Vec;
62use core::fmt;
63
64use log::warn;
65
66mod selector;
67mod stream;
68
69pub use selector::*;
70use stream::Stream;
71
72/// A list of possible errors.
73#[derive(Clone, Copy, PartialEq, Debug)]
74pub enum Error {
75    /// The steam ended earlier than we expected.
76    ///
77    /// Should only appear on invalid input data.
78    UnexpectedEndOfStream,
79
80    /// An invalid ident.
81    InvalidIdent(TextPos),
82
83    /// An unclosed comment.
84    InvalidComment(TextPos),
85
86    /// An invalid declaration value.
87    InvalidValue(TextPos),
88
89    /// An invalid byte.
90    #[allow(missing_docs)]
91    InvalidByte {
92        expected: u8,
93        actual: u8,
94        pos: TextPos,
95    },
96
97    /// A missing selector.
98    SelectorMissing,
99
100    /// An unexpected selector.
101    UnexpectedSelector,
102
103    /// An unexpected combinator.
104    UnexpectedCombinator,
105
106    /// An invalid or unsupported attribute selector.
107    InvalidAttributeSelector,
108
109    /// An invalid language pseudo-class.
110    InvalidLanguagePseudoClass,
111}
112
113impl fmt::Display for Error {
114    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
115        match *self {
116            Error::UnexpectedEndOfStream => {
117                write!(f, "unexpected end of stream")
118            }
119            Error::InvalidIdent(pos) => {
120                write!(f, "invalid ident at {}", pos)
121            }
122            Error::InvalidComment(pos) => {
123                write!(f, "invalid comment at {}", pos)
124            }
125            Error::InvalidValue(pos) => {
126                write!(f, "invalid value at {}", pos)
127            }
128            Error::InvalidByte {
129                expected,
130                actual,
131                pos,
132            } => {
133                write!(
134                    f,
135                    "expected '{}' not '{}' at {}",
136                    expected as char, actual as char, pos
137                )
138            }
139            Error::SelectorMissing => {
140                write!(f, "selector missing")
141            }
142            Error::UnexpectedSelector => {
143                write!(f, "unexpected selector")
144            }
145            Error::UnexpectedCombinator => {
146                write!(f, "unexpected combinator")
147            }
148            Error::InvalidAttributeSelector => {
149                write!(f, "invalid or unsupported attribute selector")
150            }
151            Error::InvalidLanguagePseudoClass => {
152                write!(f, "invalid language pseudo-class")
153            }
154        }
155    }
156}
157
158#[cfg(feature = "std")]
159impl std::error::Error for Error {}
160
161/// A position in text.
162///
163/// Position indicates a row/line and a column in the original text. Starting from 1:1.
164#[derive(Clone, Copy, PartialEq, Debug)]
165#[allow(missing_docs)]
166pub struct TextPos {
167    pub row: u32,
168    pub col: u32,
169}
170
171impl TextPos {
172    /// Constructs a new `TextPos`.
173    ///
174    /// Should not be invoked manually, but rather via `Stream::gen_text_pos`.
175    pub fn new(row: u32, col: u32) -> TextPos {
176        TextPos { row, col }
177    }
178}
179
180impl fmt::Display for TextPos {
181    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
182        write!(f, "{}:{}", self.row, self.col)
183    }
184}
185
186/// A declaration.
187#[derive(Clone, Copy, PartialEq, Debug)]
188#[allow(missing_docs)]
189pub struct Declaration<'a> {
190    pub name: &'a str,
191    pub value: &'a str,
192    pub important: bool,
193}
194
195/// A rule.
196#[derive(Clone, Debug)]
197pub struct Rule<'a> {
198    /// A rule selector.
199    pub selector: Selector<'a>,
200    /// A rule declarations.
201    pub declarations: Vec<Declaration<'a>>,
202}
203
204/// A style sheet.
205#[derive(Clone, Debug)]
206pub struct StyleSheet<'a> {
207    /// A list of rules.
208    pub rules: Vec<Rule<'a>>,
209}
210
211impl<'a> StyleSheet<'a> {
212    /// Creates an empty style sheet.
213    pub fn new() -> Self {
214        StyleSheet { rules: Vec::new() }
215    }
216
217    /// Parses a style sheet from text.
218    ///
219    /// At-rules are not supported and will be skipped.
220    ///
221    /// # Errors
222    ///
223    /// Doesn't produce any errors. In worst case scenario will return an empty stylesheet.
224    ///
225    /// All warnings will be logged.
226    pub fn parse(text: &'a str) -> Self {
227        let mut sheet = StyleSheet::new();
228        sheet.parse_more(text);
229        sheet
230    }
231
232    /// Parses a style sheet from a text to the current style sheet.
233    pub fn parse_more(&mut self, text: &'a str) {
234        let mut s = Stream::from(text);
235
236        if s.skip_spaces_and_comments().is_err() {
237            return;
238        }
239
240        while !s.at_end() {
241            if s.skip_spaces_and_comments().is_err() {
242                break;
243            }
244
245            let _ = consume_statement(&mut s, &mut self.rules);
246        }
247
248        if !s.at_end() {
249            warn!("{} bytes were left.", s.slice_tail().len());
250        }
251
252        // Remove empty rules.
253        self.rules.retain(|rule| !rule.declarations.is_empty());
254
255        // Sort the rules by specificity.
256        self.rules
257            .sort_by_cached_key(|rule| rule.selector.specificity());
258    }
259}
260
261impl fmt::Display for StyleSheet<'_> {
262    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
263        for (i, rule) in self.rules.iter().enumerate() {
264            write!(f, "{} {{ ", rule.selector)?;
265            for dec in &rule.declarations {
266                write!(f, "{}:{}", dec.name, dec.value)?;
267                if dec.important {
268                    write!(f, " !important")?;
269                }
270                write!(f, ";")?;
271            }
272            write!(f, " }}")?;
273
274            if i != self.rules.len() - 1 {
275                writeln!(f)?;
276            }
277        }
278
279        Ok(())
280    }
281}
282
283impl Default for StyleSheet<'_> {
284    fn default() -> Self {
285        Self::new()
286    }
287}
288
289fn consume_statement<'a>(s: &mut Stream<'a>, rules: &mut Vec<Rule<'a>>) -> Result<(), Error> {
290    if s.curr_byte() == Ok(b'@') {
291        s.advance(1);
292        consume_at_rule(s)
293    } else {
294        consume_rule_set(s, rules)
295    }
296}
297
298fn consume_at_rule(s: &mut Stream<'_>) -> Result<(), Error> {
299    let ident = s.consume_ident()?;
300    warn!("The @{} rule is not supported. Skipped.", ident);
301
302    s.skip_bytes(|c| c != b';' && c != b'{');
303
304    match s.curr_byte()? {
305        b';' => s.advance(1),
306        b'{' => consume_block(s),
307        _ => {}
308    }
309
310    Ok(())
311}
312
313fn consume_rule_set<'a>(s: &mut Stream<'a>, rules: &mut Vec<Rule<'a>>) -> Result<(), Error> {
314    let start_rule_idx = rules.len();
315
316    while s.curr_byte()? == b',' || start_rule_idx == rules.len() {
317        if s.curr_byte()? == b',' {
318            s.advance(1);
319        }
320
321        let (selector, offset) = parse(s.slice_tail());
322        s.advance(offset);
323        s.skip_spaces();
324
325        if let Some(selector) = selector {
326            rules.push(Rule {
327                selector,
328                declarations: Vec::new(),
329            });
330        }
331
332        match s.curr_byte()? {
333            b'{' => break,
334            b',' => {}
335            _ => {
336                s.skip_bytes(|c| c != b'{');
337                break;
338            }
339        }
340    }
341
342    s.try_consume_byte(b'{');
343
344    let declarations = consume_declarations(s)?;
345    for rule in rules.iter_mut().skip(start_rule_idx) {
346        rule.declarations = declarations.clone();
347    }
348
349    s.try_consume_byte(b'}');
350
351    Ok(())
352}
353
354fn consume_block(s: &mut Stream<'_>) {
355    s.try_consume_byte(b'{');
356    consume_until_block_end(s);
357}
358
359fn consume_until_block_end(s: &mut Stream<'_>) {
360    // Block can have nested blocks, so we have to check for matching braces.
361    // We simply counting the number of opening braces, which is incorrect,
362    // since `{` can be inside a string, but it's fine for majority of the cases.
363
364    let mut braces = 0;
365    while !s.at_end() {
366        match s.curr_byte_unchecked() {
367            b'{' => {
368                braces += 1;
369            }
370            b'}' => {
371                if braces == 0 {
372                    break;
373                } else {
374                    braces -= 1;
375                }
376            }
377            _ => {}
378        }
379
380        s.advance(1);
381    }
382
383    s.try_consume_byte(b'}');
384}
385
386fn consume_declarations<'a>(s: &mut Stream<'a>) -> Result<Vec<Declaration<'a>>, Error> {
387    let mut declarations = Vec::new();
388
389    while !s.at_end() && s.curr_byte() != Ok(b'}') {
390        match consume_declaration(s) {
391            Ok(declaration) => declarations.push(declaration),
392            Err(_) => {
393                consume_until_block_end(s);
394                break;
395            }
396        }
397    }
398
399    Ok(declarations)
400}
401
402/// A declaration tokenizer.
403///
404/// Tokenizer will stop at the first invalid token.
405///
406/// # Example
407///
408/// ```
409/// use simplecss::{DeclarationTokenizer, Declaration};
410///
411/// let mut t = DeclarationTokenizer::from("background: url(\"img.png\"); color:red !important");
412/// assert_eq!(t.next().unwrap(), Declaration { name: "background", value: "url(\"img.png\")", important: false });
413/// assert_eq!(t.next().unwrap(), Declaration { name: "color", value: "red", important: true });
414/// ```
415pub struct DeclarationTokenizer<'a> {
416    stream: Stream<'a>,
417}
418
419impl<'a> From<&'a str> for DeclarationTokenizer<'a> {
420    fn from(text: &'a str) -> Self {
421        DeclarationTokenizer {
422            stream: Stream::from(text),
423        }
424    }
425}
426
427impl<'a> Iterator for DeclarationTokenizer<'a> {
428    type Item = Declaration<'a>;
429
430    fn next(&mut self) -> Option<Self::Item> {
431        let _ = self.stream.skip_spaces_and_comments();
432
433        if self.stream.at_end() {
434            return None;
435        }
436
437        match consume_declaration(&mut self.stream) {
438            Ok(v) => Some(v),
439            Err(_) => {
440                self.stream.jump_to_end();
441                None
442            }
443        }
444    }
445}
446
447fn consume_declaration<'a>(s: &mut Stream<'a>) -> Result<Declaration<'a>, Error> {
448    s.skip_spaces_and_comments()?;
449
450    // Parse name.
451
452    // https://snook.ca/archives/html_and_css/targetting_ie7
453    if s.curr_byte() == Ok(b'*') {
454        s.advance(1);
455    }
456
457    let name = s.consume_ident()?;
458
459    s.skip_spaces_and_comments()?;
460    s.consume_byte(b':')?;
461    s.skip_spaces_and_comments()?;
462
463    // Parse value.
464    let start = s.pos();
465    let mut end = s.pos();
466    while consume_term(s).is_ok() {
467        end = s.pos();
468        s.skip_spaces_and_comments()?;
469    }
470    let value = s.slice_range(start, end).trim();
471
472    s.skip_spaces_and_comments()?;
473
474    // Check for `important`.
475    let mut important = false;
476    if s.curr_byte() == Ok(b'!') {
477        s.advance(1);
478        s.skip_spaces_and_comments()?;
479        if s.slice_tail().starts_with("important") {
480            s.advance(9);
481            important = true;
482        }
483    }
484
485    s.skip_spaces_and_comments()?;
486
487    while s.curr_byte() == Ok(b';') {
488        s.advance(1);
489        s.skip_spaces_and_comments()?;
490    }
491
492    s.skip_spaces_and_comments()?;
493
494    if value.is_empty() {
495        return Err(Error::InvalidValue(s.gen_text_pos_from(start)));
496    }
497
498    Ok(Declaration {
499        name,
500        value,
501        important,
502    })
503}
504
505fn consume_term(s: &mut Stream<'_>) -> Result<(), Error> {
506    fn consume_digits(s: &mut Stream<'_>) {
507        while let Ok(b'0'..=b'9') = s.curr_byte() {
508            s.advance(1);
509        }
510    }
511
512    match s.curr_byte()? {
513        b'#' => {
514            s.advance(1);
515            match s.consume_ident() {
516                Ok(_) => {}
517                Err(_) => {
518                    // Try consume as a hex color.
519                    while let Ok(c) = s.curr_byte() {
520                        match c {
521                            b'0'..=b'9' | b'a'..=b'f' | b'A'..=b'F' => s.advance(1),
522                            _ => break,
523                        }
524                    }
525                }
526            }
527        }
528        b'+' | b'-' | b'0'..=b'9' | b'.' => {
529            // Consume number.
530
531            s.advance(1);
532            consume_digits(s);
533            if s.curr_byte() == Ok(b'.') {
534                s.advance(1);
535                consume_digits(s);
536            }
537
538            if s.curr_byte() == Ok(b'%') {
539                s.advance(1);
540            } else {
541                // Consume suffix if any.
542                let _ = s.consume_ident();
543            }
544        }
545        b'\'' | b'"' => {
546            s.consume_string()?;
547        }
548        b',' => {
549            s.advance(1);
550        }
551        _ => {
552            let _ = s.consume_ident()?;
553
554            // Consume function.
555            if s.curr_byte() == Ok(b'(') {
556                s.skip_bytes(|c| c != b')');
557                s.consume_byte(b')')?;
558            }
559        }
560    }
561
562    Ok(())
563}