1use {
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#[derive(Debug, Clone)]
30pub struct Stylesheet {
31 pub rules: CssRules,
33
34 pub source_map_url: Option<String>,
36
37 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 #[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 #[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 #[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 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 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 #[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 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}