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
+ ) ]
1
100
use crate :: parse:: Declaration ;
2
101
use kuchiki:: traits:: TendrilSink ;
3
102
use kuchiki:: { parse_html, ElementData , NodeDataRef , Selectors } ;
4
- use std:: io;
5
103
104
+ pub mod error;
6
105
mod parse;
7
106
107
+ pub use error:: InlineError ;
108
+
8
109
#[ derive( Debug ) ]
9
110
struct Rule {
10
- selectors : kuchiki :: Selectors ,
111
+ selectors : Selectors ,
11
112
declarations : Vec < Declaration > ,
12
113
}
13
114
@@ -20,7 +121,7 @@ impl Rule {
20
121
}
21
122
}
22
123
23
- fn process_style_node ( node : NodeDataRef < ElementData > ) -> Vec < Rule > {
124
+ fn process_style_node ( node : & NodeDataRef < ElementData > ) -> Vec < Rule > {
24
125
let css = node. text_contents ( ) ;
25
126
let mut parse_input = cssparser:: ParserInput :: new ( css. as_str ( ) ) ;
26
127
let mut parser = parse:: CSSParser :: new ( & mut parse_input) ;
@@ -31,16 +132,17 @@ fn process_style_node(node: NodeDataRef<ElementData>) -> Vec<Rule> {
31
132
. ok ( )
32
133
} )
33
134
. collect :: < Result < Vec < _ > , _ > > ( )
34
- . unwrap ( )
135
+ . map_err ( |_| error:: InlineError :: ParseError )
136
+ . expect ( "Parsing error" ) // Should return Result instead
35
137
}
36
138
37
139
/// 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 > {
39
141
let document = parse_html ( ) . one ( html) ;
40
142
let rules = document
41
143
. select ( "style" )
42
- . unwrap ( )
43
- . map ( process_style_node)
144
+ . map_err ( |_| error :: InlineError :: ParseError ) ?
145
+ . map ( | ref node| process_style_node ( node ) )
44
146
. flatten ( ) ;
45
147
46
148
for rule in rules {
@@ -63,9 +165,9 @@ pub fn inline(html: &str) -> Result<String, io::Error> {
63
165
let mut out = vec ! [ ] ;
64
166
document
65
167
. select ( "html" )
66
- . unwrap ( )
168
+ . map_err ( |_| error :: InlineError :: ParseError ) ?
67
169
. next ( )
68
- . unwrap ( )
170
+ . expect ( "HTML tag should be present" ) // Should it?
69
171
. as_node ( )
70
172
. serialize ( & mut out) ?;
71
173
Ok ( String :: from_utf8_lossy ( & out) . to_string ( ) )
@@ -96,7 +198,7 @@ p.footer { font-size: 1px}
96
198
97
199
#[ test]
98
200
fn test_inline ( ) {
99
- let inlined = inline ( HTML ) . unwrap ( ) ;
201
+ let inlined = inline ( HTML ) . expect ( "Should be valid" ) ;
100
202
assert_eq ! (
101
203
inlined,
102
204
r#"<html><head>
0 commit comments