1+ //! # css-inline
2+ //!
3+ //! A crate for inlining CSS into HTML documents. When you send HTML emails you need to use "style"
4+ //! attributes instead of "style" tags.
5+ //!
6+ //! For example, this HTML:
7+ //!
8+ //! ```html
9+ //! <html>
10+ //! <head>
11+ //! <title>Test</title>
12+ //! <style>
13+ //! h1, h2 { color:blue; }
14+ //! strong { text-decoration:none }
15+ //! p { font-size:2px }
16+ //! p.footer { font-size: 1px}
17+ //! </style>
18+ //! </head>
19+ //! <body>
20+ //! <h1>Big Text</h1>
21+ //! <p>
22+ //! <strong>Solid</strong>
23+ //! </p>
24+ //! <p class="footer">Foot notes</p>
25+ //! </body>
26+ //! </html>
27+ //! ```
28+ //!
29+ //! Will be turned into this:
30+ //!
31+ //! ```html
32+ //! <html>
33+ //! <head>
34+ //! <title>Test</title>
35+ //! </head>
36+ //! <body>
37+ //! <h1 style="color:blue;">Big Text</h1>
38+ //! <p style="font-size:2px;">
39+ //! <strong style="text-decoration:none;">Solid</strong>
40+ //! </p>
41+ //! <p style="font-size:1px;">Foot notes</p>
42+ //! </body>
43+ //! </html>
44+ //! ```
45+ //!
46+ //! ## Example:
47+ //!
48+ //! ```rust
49+ //! const HTML: &str = r#"<html>
50+ //! <head>
51+ //! <title>Test</title>
52+ //! <style>
53+ //! h1, h2 { color:blue; }
54+ //! strong { text-decoration:none }
55+ //! p { font-size:2px }
56+ //! p.footer { font-size: 1px}
57+ //! </style>
58+ //! </head>
59+ //! <body>
60+ //! <h1>Big Text</h1>
61+ //! <p>
62+ //! <strong>Solid</strong>
63+ //! </p>
64+ //! <p class="footer">Foot notes</p>
65+ //! </body>
66+ //! </html>"#;
67+ //!
68+ //!fn main() -> Result<(), css_inline::InlineError> {
69+ //! let inlined = css_inline::inline(HTML)?;
70+ //! // Do something with inlined HTML, e.g. send an email
71+ //! Ok(())
72+ //! }
73+ //!
74+ //! ```
75+ #![ warn(
76+ clippy:: doc_markdown,
77+ clippy:: redundant_closure,
78+ clippy:: explicit_iter_loop,
79+ clippy:: match_same_arms,
80+ clippy:: needless_borrow,
81+ clippy:: print_stdout,
82+ clippy:: integer_arithmetic,
83+ clippy:: cast_possible_truncation,
84+ clippy:: result_unwrap_used,
85+ clippy:: result_map_unwrap_or_else,
86+ clippy:: option_unwrap_used,
87+ clippy:: option_map_unwrap_or_else,
88+ clippy:: option_map_unwrap_or,
89+ clippy:: trivially_copy_pass_by_ref,
90+ clippy:: needless_pass_by_value,
91+ missing_docs,
92+ missing_debug_implementations,
93+ trivial_casts,
94+ trivial_numeric_casts,
95+ unused_extern_crates,
96+ unused_import_braces,
97+ unused_qualifications,
98+ variant_size_differences
99+ ) ]
1100use crate :: parse:: Declaration ;
2101use kuchiki:: traits:: TendrilSink ;
3102use kuchiki:: { parse_html, ElementData , NodeDataRef , Selectors } ;
4- use std:: io;
5103
104+ pub mod error;
6105mod parse;
7106
107+ pub use error:: InlineError ;
108+
8109#[ derive( Debug ) ]
9110struct Rule {
10- selectors : kuchiki :: Selectors ,
111+ selectors : Selectors ,
11112 declarations : Vec < Declaration > ,
12113}
13114
@@ -20,7 +121,7 @@ impl Rule {
20121 }
21122}
22123
23- fn process_style_node ( node : NodeDataRef < ElementData > ) -> Vec < Rule > {
124+ fn process_style_node ( node : & NodeDataRef < ElementData > ) -> Vec < Rule > {
24125 let css = node. text_contents ( ) ;
25126 let mut parse_input = cssparser:: ParserInput :: new ( css. as_str ( ) ) ;
26127 let mut parser = parse:: CSSParser :: new ( & mut parse_input) ;
@@ -31,16 +132,17 @@ fn process_style_node(node: NodeDataRef<ElementData>) -> Vec<Rule> {
31132 . ok ( )
32133 } )
33134 . collect :: < Result < Vec < _ > , _ > > ( )
34- . unwrap ( )
135+ . map_err ( |_| error:: InlineError :: ParseError )
136+ . expect ( "Parsing error" ) // Should return Result instead
35137}
36138
37139/// Inline CSS styles from <style> tags to matching elements in the HTML tree.
38- pub fn inline ( html : & str ) -> Result < String , io :: Error > {
140+ pub fn inline ( html : & str ) -> Result < String , InlineError > {
39141 let document = parse_html ( ) . one ( html) ;
40142 let rules = document
41143 . select ( "style" )
42- . unwrap ( )
43- . map ( process_style_node)
144+ . map_err ( |_| error :: InlineError :: ParseError ) ?
145+ . map ( | ref node| process_style_node ( node ) )
44146 . flatten ( ) ;
45147
46148 for rule in rules {
@@ -63,9 +165,9 @@ pub fn inline(html: &str) -> Result<String, io::Error> {
63165 let mut out = vec ! [ ] ;
64166 document
65167 . select ( "html" )
66- . unwrap ( )
168+ . map_err ( |_| error :: InlineError :: ParseError ) ?
67169 . next ( )
68- . unwrap ( )
170+ . expect ( "HTML tag should be present" ) // Should it?
69171 . as_node ( )
70172 . serialize ( & mut out) ?;
71173 Ok ( String :: from_utf8_lossy ( & out) . to_string ( ) )
@@ -96,7 +198,7 @@ p.footer { font-size: 1px}
96198
97199 #[ test]
98200 fn test_inline ( ) {
99- let inlined = inline ( HTML ) . unwrap ( ) ;
201+ let inlined = inline ( HTML ) . expect ( "Should be valid" ) ;
100202 assert_eq ! (
101203 inlined,
102204 r#"<html><head>
0 commit comments