Skip to content

Commit 6cd68dd

Browse files
committed
feat: CSSInliner and customization options
Ref: #9
1 parent 2db0de2 commit 6cd68dd

File tree

2 files changed

+112
-39
lines changed

2 files changed

+112
-39
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
## [Unreleased]
44

5+
### Added
6+
7+
- `CSSInliner` and customization options. [#9](https://github.com/Stranger6667/css-inline/issues/9)
8+
59
### Changed
610

711
- Improved error messages. [#27](https://github.com/Stranger6667/css-inline/issues/27)

src/lib.rs

Lines changed: 108 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -66,12 +66,42 @@
6666
//! </html>"#;
6767
//!
6868
//!fn main() -> Result<(), css_inline::InlineError> {
69-
//! let inlined = css_inline::inline(HTML)?;
69+
//! let inlined = css_inline::inline(HTML)?; // shortcut with default options
7070
//! // Do something with inlined HTML, e.g. send an email
7171
//! Ok(())
7272
//! }
73-
//!
7473
//! ```
74+
//!
75+
//! Or if you need more control over inlining you could use `CSSInliner`:
76+
//!
77+
//! ```rust
78+
//! const HTML: &str = r#"<html>
79+
//! <head>
80+
//! <title>Test</title>
81+
//! <style>
82+
//! h1, h2 { color:blue; }
83+
//! strong { text-decoration:none }
84+
//! p { font-size:2px }
85+
//! p.footer { font-size: 1px}
86+
//! </style>
87+
//! </head>
88+
//! <body>
89+
//! <h1>Big Text</h1>
90+
//! <p>
91+
//! <strong>Solid</strong>
92+
//! </p>
93+
//! <p class="footer">Foot notes</p>
94+
//! </body>
95+
//! </html>"#;
96+
//!
97+
//!fn main() -> Result<(), css_inline::InlineError> {
98+
//! let options = css_inline::InlineOptions {};
99+
//! let inliner = css_inline::CSSInliner::new(options);
100+
//! let inlined = inliner.inline(HTML)?;
101+
//! // Do something with inlined HTML, e.g. send an email
102+
//! Ok(())
103+
//! }
104+
//!```
75105
#![warn(
76106
clippy::doc_markdown,
77107
clippy::redundant_closure,
@@ -122,49 +152,88 @@ impl Rule {
122152
}
123153
}
124154

125-
/// Inline CSS styles from <style> tags to matching elements in the HTML tree.
126-
pub fn inline(html: &str) -> Result<String, InlineError> {
127-
let document = parse_html().one(html);
128-
for style_tag in document
129-
.select("style")
130-
.map_err(|_| error::InlineError::ParseError("Unknown error".to_string()))?
131-
{
132-
if let Some(first_child) = style_tag.as_node().first_child() {
133-
if let Some(css_cell) = first_child.as_text() {
134-
let css = css_cell.borrow();
135-
let mut parse_input = cssparser::ParserInput::new(css.as_str());
136-
let mut parser = parse::CSSParser::new(&mut parse_input);
137-
for parsed in parser.parse() {
138-
if let Ok((selector, declarations)) = parsed {
139-
let rule = Rule::new(&selector, declarations).map_err(|_| {
140-
error::InlineError::ParseError("Unknown error".to_string())
141-
})?;
142-
let matching_elements = document
143-
.inclusive_descendants()
144-
.filter_map(|node| node.into_element_ref())
145-
.filter(|element| rule.selectors.matches(element));
146-
for matching_element in matching_elements {
147-
let mut attributes = matching_element.attributes.borrow_mut();
148-
let style = if let Some(existing_style) = attributes.get("style") {
149-
merge_styles(existing_style, &rule.declarations)?
150-
} else {
151-
rule.declarations
152-
.iter()
153-
.map(|&(ref key, ref value)| format!("{}:{};", key, value))
154-
.collect()
155-
};
156-
attributes.insert("style", style);
155+
/// Configuration options for CSS inlining process.
156+
#[derive(Debug)]
157+
pub struct InlineOptions {}
158+
159+
impl Default for InlineOptions {
160+
#[inline]
161+
fn default() -> Self {
162+
InlineOptions {}
163+
}
164+
}
165+
166+
/// Customizable CSS inliner.
167+
#[derive(Debug)]
168+
pub struct CSSInliner {
169+
options: InlineOptions,
170+
}
171+
172+
impl CSSInliner {
173+
/// Create a new `CSSInliner` instance with given options.
174+
#[inline]
175+
pub fn new(options: InlineOptions) -> Self {
176+
CSSInliner { options }
177+
}
178+
179+
/// Inline CSS styles from <style> tags to matching elements in the HTML tree.
180+
#[inline]
181+
pub fn inline(&self, html: &str) -> Result<String, InlineError> {
182+
let document = parse_html().one(html);
183+
for style_tag in document
184+
.select("style")
185+
.map_err(|_| error::InlineError::ParseError("Unknown error".to_string()))?
186+
{
187+
if let Some(first_child) = style_tag.as_node().first_child() {
188+
if let Some(css_cell) = first_child.as_text() {
189+
let css = css_cell.borrow();
190+
let mut parse_input = cssparser::ParserInput::new(css.as_str());
191+
let mut parser = parse::CSSParser::new(&mut parse_input);
192+
for parsed in parser.parse() {
193+
if let Ok((selector, declarations)) = parsed {
194+
let rule = Rule::new(&selector, declarations).map_err(|_| {
195+
error::InlineError::ParseError("Unknown error".to_string())
196+
})?;
197+
let matching_elements = document
198+
.inclusive_descendants()
199+
.filter_map(|node| node.into_element_ref())
200+
.filter(|element| rule.selectors.matches(element));
201+
for matching_element in matching_elements {
202+
let mut attributes = matching_element.attributes.borrow_mut();
203+
let style = if let Some(existing_style) = attributes.get("style") {
204+
merge_styles(existing_style, &rule.declarations)?
205+
} else {
206+
rule.declarations
207+
.iter()
208+
.map(|&(ref key, ref value)| format!("{}:{};", key, value))
209+
.collect()
210+
};
211+
attributes.insert("style", style);
212+
}
157213
}
214+
// Ignore not parsable entries. E.g. there is no parser for @media queries
215+
// Which means that they will fall into this category and will be ignored
158216
}
159-
// Ignore not parsable entries. E.g. there is no parser for @media queries
160-
// Which means that they will fall into this category and will be ignored
161217
}
162218
}
163219
}
220+
let mut out = vec![];
221+
document.serialize(&mut out)?;
222+
Ok(String::from_utf8_lossy(&out).to_string())
164223
}
165-
let mut out = vec![];
166-
document.serialize(&mut out)?;
167-
Ok(String::from_utf8_lossy(&out).to_string())
224+
}
225+
226+
impl Default for CSSInliner {
227+
#[inline]
228+
fn default() -> Self {
229+
CSSInliner::new(Default::default())
230+
}
231+
}
232+
233+
/// Shortcut for inlining CSS with default parameters.
234+
#[inline]
235+
pub fn inline(html: &str) -> Result<String, InlineError> {
236+
CSSInliner::default().inline(html)
168237
}
169238

170239
fn merge_styles(existing_style: &str, new_styles: &[Declaration]) -> Result<String, InlineError> {

0 commit comments

Comments
 (0)