lewp_css/
stylesheet.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    crate::{
6        blocking_io_only_std_fmt_write_to_std_io_write_adaptor::BlockingIoOnlyStdFmtWriteToStdIoWriteAdaptor,
7        domain::{
8            at_rules::namespace::Namespaces,
9            CssRule,
10            CssRules,
11            HasCssRules,
12        },
13        parsers::{
14            top_level_rule_parser::TopLevelRuleParser,
15            ParserContext,
16            ParsingMode,
17            State,
18        },
19        quick_error::ResultExt,
20        CustomParseError,
21        StylesheetError,
22    },
23    cssparser::{ParseError, Parser, ParserInput, RuleListParser, ToCss},
24    std::{fmt, fs::File, io::Read, path::Path},
25};
26
27/// Represents an entire CSS stylesheet.
28/// The values of property declarations are currently stored as a string. Parsing property declarations is a monster job. If you feel like helping...
29#[derive(Debug, Clone)]
30pub struct Stylesheet {
31    /// The stylesheet's rules.
32    pub rules: CssRules,
33
34    /// An optional source map for this stylesheet.
35    pub source_map_url: Option<String>,
36
37    /// An optional source URL for this stylesheet.
38    pub source_url: Option<String>,
39}
40
41impl HasCssRules for Stylesheet {
42    #[inline(always)]
43    fn css_rules(&self) -> &CssRules {
44        &self.rules
45    }
46
47    #[inline(always)]
48    fn css_rules_mut(&mut self) -> &mut CssRules {
49        &mut self.rules
50    }
51
52    #[inline(always)]
53    fn css_rules_slice(&self) -> &[CssRule] {
54        &self.rules.0[..]
55    }
56
57    #[inline(always)]
58    fn css_rules_vec(&self) -> &Vec<CssRule> {
59        &self.rules.0
60    }
61
62    #[inline(always)]
63    fn css_rules_vec_mut(&mut self) -> &mut Vec<CssRule> {
64        &mut self.rules.0
65    }
66}
67
68impl Stylesheet {
69    /// Serializes a Stylesheet to a file path, optionally including source-map and source-url comments.
70    /// Will create or truncate `stylesheet_file_path` as required.
71    /// Convenience method wrapped `to_css()`.
72    #[inline(always)]
73    pub fn to_file_path<P: AsRef<Path>>(
74        &self,
75        stylesheet_file_path: P,
76        include_source_urls: bool,
77    ) -> Result<(), StylesheetError> {
78        let path = stylesheet_file_path.as_ref();
79        let file = File::create(path).context(path)?;
80        self.to_css(
81            &mut BlockingIoOnlyStdFmtWriteToStdIoWriteAdaptor(file),
82            include_source_urls,
83        )
84        .context(path)?;
85        Ok(())
86    }
87
88    /// Serializes a Stylesheet as a string, optionally including source-map and source-url comments.
89    /// Convenience method wrapped `to_css()`.
90    #[inline(always)]
91    pub fn to_css_string(&self, include_source_urls: bool) -> String {
92        let mut string = String::new();
93        self.to_css(&mut string, include_source_urls).unwrap();
94        string
95    }
96
97    /// Serializes a Stylesheet to a vector of UTF-8 encoded bytes.
98    /// Convenience method wrapped `to_css_string()`.
99    #[inline(always)]
100    pub fn to_bytes(&self, include_source_urls: bool) -> Vec<u8> {
101        self.to_css_string(include_source_urls).into_bytes()
102    }
103
104    /// Serializes a Stylesheet, optionally including source-map and source-url comments.
105    pub fn to_css<W: fmt::Write>(
106        &self,
107        destination: &mut W,
108        include_source_urls: bool,
109    ) -> fmt::Result {
110        if include_source_urls {
111            // An older convention was to use '@' instead of '#'
112
113            if let Some(ref source_map_url) = self.source_map_url {
114                writeln!(
115                    destination,
116                    "//# sourceMappingURL=<{}>",
117                    source_map_url
118                )?;
119            }
120
121            if let Some(ref source_url) = self.source_url {
122                writeln!(destination, "//# sourceURL=<{}>", source_url)?;
123            }
124        }
125
126        self.rules.to_css(destination)?;
127
128        Ok(())
129    }
130
131    /// Loads and parses a Stylesheet.
132    #[inline(always)]
133    pub fn from_file_path<P: AsRef<Path>>(
134        html_document_file_path: P,
135    ) -> Result<Self, StylesheetError> {
136        let path = html_document_file_path.as_ref();
137        let metadata = path.metadata().context(path)?;
138
139        let mut file = File::open(path).context(path)?;
140        let mut css = String::with_capacity(metadata.len() as usize);
141        file.read_to_string(&mut css).context(path)?;
142
143        let result = Self::parse(&css);
144
145        match result {
146            Ok(stylesheet) => Ok(stylesheet),
147            Err(cause) => Err(StylesheetError::Parse(
148                path.to_path_buf(),
149                cause.location,
150                format!("{:?}", cause),
151            )),
152        }
153    }
154
155    /// Parses a string of CSS to produce a stylesheet.
156    /// Can be used with the contents of a CSS file.
157    /// Assumes the string is UTF-8 encoded.
158    /// Does not use a stream of bytes as parsing CSS involves going backwards and forwards a lot... CSS parsing is somewhat evil and is not particularly efficient.
159    /// The parser does apply a few small modifications to the incoming CSS, normalizing some pseudo-class, psuedo-element and media query names.
160    /// The parser does not parse properties as such, simply keeping them as a CSS string. Hopefully it will one day - there are only 200 odd specialist rules to implement.
161    pub fn parse(css: &str) -> Result<Self, ParseError<CustomParseError>> {
162        const LineNumberingIsZeroBased: u32 = 0;
163
164        let mut parserInput = ParserInput::new_with_line_number_offset(
165            css,
166            LineNumberingIsZeroBased,
167        );
168        let mut input = Parser::new(&mut parserInput);
169
170        let mut rules = Vec::new();
171
172        let topLevelRuleParser = TopLevelRuleParser {
173            context: ParserContext {
174                rule_type: None,
175                parsing_mode: ParsingMode::Default,
176            },
177            state: State::Start,
178            namespaces: Namespaces::empty(),
179        };
180
181        {
182            let iter = RuleListParser::new_for_stylesheet(
183                &mut input,
184                topLevelRuleParser,
185            );
186
187            for result in iter {
188                match result {
189                    Ok(rule) => rules.push(rule),
190                    Err(preciseParseError) => return Err(preciseParseError.0),
191                }
192            }
193        }
194
195        Ok(Self {
196            rules: CssRules(rules),
197            source_map_url: input.current_source_map_url().map(String::from),
198            source_url: input.current_source_url().map(String::from),
199        })
200    }
201}