1#[macro_use]
2extern crate lazy_static;
3extern crate regex;
4
5
6use regex::{Captures, Regex};
7
8#[cfg(test)]
9mod tests {
10
11 use super::*;
12
13 mod font_manipulation {
14 use super::*;
15 #[test]
16 fn bold() {
17 assert_eq!("[b]test[/b]".as_html(), "<strong>test</strong>")
18 }
19
20 #[test]
21 fn italic() {
22 assert_eq!("[i]test[/i]".as_html(), "<em>test</em>")
23 }
24
25 #[test]
26 fn underline() {
27 assert_eq!("[u]test[/u]".as_html(), r#"<u>test</u>"#)
28 }
29
30 #[test]
31 fn strikethrough() {
32 assert_eq!("[s]test[/s]".as_html(), r#"<strike>test</strike>"#)
33 }
34
35 #[test]
36 fn color() {
37 assert_eq!("[color=red]test[/color]".as_html(),
38 r#"<span style="color: red;">test</span>"#)
39 }
40
41 #[test]
42 fn size() {
43 assert_eq!("[size=10]test[/size]".as_html(),
44 r#"<span style="font-size: 10px;">test</span>"#)
45 }
46 }
47
48 mod alignment {
49 use super::*;
50 #[test]
51 fn left() {
52 assert_eq!("[left]test[/left]".as_html(),
53 r#"<div style="text-align: left;">test</div>"#)
54 }
55
56 #[test]
57 fn right() {
58 assert_eq!("[right]test[/right]".as_html(),
59 r#"<div style="text-align: right;">test</div>"#)
60 }
61
62 #[test]
63 fn center() {
64 assert_eq!("[center]test[/center]".as_html(),
65 r#"<div style="text-align: center;">test</div>"#)
66 }
67 }
68
69 #[test]
70 fn quote() {
71 assert_eq!("[quote]test[/quote]".as_html(),
72 r#"<div class="quote">test</div>"#)
73 }
74
75 #[test]
76 fn named_quote() {
77 assert_eq!("[quote=Author]test[/quote]".as_html(),
78 r#"<div class="quote"><strong>Author wrote:</strong>
79test</div>"#)
80 }
81
82 #[test]
83 fn link() {
84 assert_eq!("[url]test[/url]".as_html(),
85 r#"<a href="test" rel="nofollow" target="_new">test</a>"#)
86 }
87
88 #[test]
89 fn named_link() {
90 assert_eq!("[url=title]test[/url]".as_html(),
91 r#"<a href="test" rel="nofollow" target="_new">title</a>"#)
92 }
93
94 mod image {
95 use super::*;
96
97 #[test]
98 fn plain() {
99 assert_eq!("[img]test[/img]".as_html(), "<img src=\"test\" />")
100 }
101
102 #[test]
103 fn named_image() {
104 assert_eq!("[img=name]test[/img]".as_html(),
105 "<img src=\"test\" alt=\"name\" />")
106 }
107
108 #[test]
109 fn resized_image() {
110 assert_eq!("[img=100x50]test[/img]".as_html(),
111 "<img src=\"test\" width=\"100\" height=\"50\" />")
112 }
113
114 #[test]
115 fn with_metadata() {
116 assert_eq!(r#"[img width="100" height="50" alt="alt" title="title"]test[/img]"#
117 .as_html(),
118 "<img src=\"test\" width=\"100\" height=\"50\" alt=\"alt\" \
119 title=\"title\" />")
120 }
121 }
122
123 mod list {
124 use super::*;
125 #[test]
126 fn ordered() {
127 assert_eq!(r#"[ol]test[/ol]"#.as_html(), r#"<ol>test</ol>"#)
128 }
129
130 #[test]
131 fn full_ordered_list() {
132 assert_eq!(r#"[ol]
133[li]Item one[/li]
134[li]Item two[/li]
135[/ol]"#
136 .as_html(),
137 r#"<ol>
138<li>Item one</li>
139<li>Item two</li>
140</ol>"#)
141 }
142
143 #[test]
144 fn unordered() {
145 assert_eq!(r#"[ul]test[/ul]"#.as_html(), r#"<ul>test</ul>"#)
146 }
147
148 #[test]
149 fn full_unordered_list() {
150 assert_eq!(r#"[ul]
151[li]Item one[/li]
152[li]Item two[/li]
153[/ul]"#
154 .as_html(),
155 r#"<ul>
156<li>Item one</li>
157<li>Item two</li>
158</ul>"#)
159 }
160
161 #[test]
162 fn plain() {
163 assert_eq!(r#"[list]test[/list]"#.as_html(), r#"<ul>test</ul>"#)
164 }
165 }
166
167 #[test]
168 fn code() {
169 assert_eq!("[code][b]test[/b][/code]".as_html(),
170 "<pre><code>[b]test[/b]</code></pre>")
171 }
172
173 #[test]
174 fn preformatted() {
175 assert_eq!("[preformatted][b]test[/b][/preformatted]".as_html(),
176 "<pre><code>[b]test[/b]</code></pre>")
177 }
178
179 mod table {
180 use super::*;
181
182 #[test]
183 fn table() {
184 assert_eq!("[table][/table]".as_html(), "<table></table>")
185 }
186
187 #[test]
188 fn complex_table() {
189 assert_eq!(r"[table]
190 [tr]
191 [th]Name[/th]
192 [th]Date[/th]
193 [/tr]
194 [tr]
195 [td]Test[/td]
196 [td]Yesterday[/td]
197 [/tr]
198[/table]"
199 .as_html(),
200 r"<table>
201 <tr>
202 <th>Name</th>
203 <th>Date</th>
204 </tr>
205 <tr>
206 <td>Test</td>
207 <td>Yesterday</td>
208 </tr>
209</table>")
210 }
211
212 #[test]
213 fn row() {
214 assert_eq!("[tr]test[/tr]".as_html(), "<tr>test</tr>")
215 }
216
217 mod content {
218 use super::*;
219 #[test]
220 fn cell() {
221 assert_eq!("[td]test[/td]".as_html(), "<td>test</td>")
222 }
223
224 #[test]
225 fn header() {
226 assert_eq!("[th]test[/th]".as_html(), "<th>test</th>")
227 }
228 }
229 }
230
231 #[test]
232 fn youtube_video() {
233 assert_eq!("[youtube]test[/youtube]".as_html(),
234 "<object data=\"http://www.youtube.com/embed/test\"></object>")
235 }
236
237 #[test]
238 fn youtube_video_with_size() {
239 assert_eq!("[youtube=560x315]test[/youtube]".as_html(),
240 "<object width=\"560\" \
241 height=\"315\" data=\"http://www.youtube.com/embed/test\"></object>")
242 }
243}
244
245pub trait BBCode {
255 fn as_html(&self) -> String;
256}
257
258fn code(input: &str) -> String {
259 let mut output = input.to_owned();
260 lazy_static!{
261 static ref CODE: Regex = Regex::new(
262 r"(?s)\[(code|preformatted)\](.*?)\[/(code|preformatted)\]"
263 ).unwrap();
264 }
265
266 output = CODE.replace_all(&output, code_replacer)
267 .to_string();
268 output
269}
270
271fn code_replacer(captures: &Captures) -> String {
272 let mut replaced = captures.get(2)
273 .unwrap()
274 .as_str()
275 .to_string();
276 for &(input, output) in [("[", "["), ("]", "]"), ("<br />", "\n")].iter() {
277 replaced = replaced.replace(&input, &output);
278 }
279 format!("<pre><code>{}</code></pre>", replaced)
280}
281
282pub fn patterns() -> &'static [(Regex, &'static str); 26] {
283 lazy_static!{
284 static ref PATTERNS: [(Regex, &'static str); 26] = [
285 (Regex::new(r"(?s)\[b\](.*?)\[/b\]").unwrap(), "<strong>$1</strong>"),
287 (Regex::new(r"(?s)\[i\](.*?)\[/i\]").unwrap(), "<em>$1</em>"),
288 (Regex::new(r"(?s)\[u\](.*?)\[/u\]").unwrap(), "<u>$1</u>"),
289 (Regex::new(r"(?s)\[s\](.*?)\[/s\]").unwrap(), "<strike>$1</strike>"),
290 (Regex::new(r"(?s)\[size=(\d+)](.*?)\[/size\]").unwrap(),
291 "<span style=\"font-size: ${1}px;\">$2</span>"),
292 (Regex::new(r"(?s)\[color=(.+)](.*?)\[/color\]").unwrap(),
293 "<span style=\"color: $1;\">$2</span>"),
294 (Regex::new(r"(?s)\[center\](.*?)\[/center\]").unwrap(),
296 "<div style=\"text-align: center;\">$1</div>"),
297 (Regex::new(r"(?s)\[left\](.*?)\[/left\]").unwrap(),
298 "<div style=\"text-align: left;\">$1</div>"),
299 (Regex::new(r"(?s)\[right\](.*?)\[/right\]").unwrap(),
300 "<div style=\"text-align: right;\">$1</div>"),
301 (Regex::new(r"(?s)\[table\](.*?)\[/table\]").unwrap(), "<table>$1</table>"),
303 (Regex::new(r"(?s)\[td\](.*?)\[/td\]").unwrap(), "<td>$1</td>"),
304 (Regex::new(r"(?s)\[tr\](.*?)\[/tr\]").unwrap(), "<tr>$1</tr>"),
305 (Regex::new(r"(?s)\[th\](.*?)\[/th\]").unwrap(), "<th>$1</th>"),
306 (Regex::new(r"(?s)\[url\](.*?)\[/url\]").unwrap(),
308 "<a href=\"$1\" rel=\"nofollow\" target=\"_new\">$1</a>"),
309 (Regex::new(r"(?s)\[url=(.+)\](.*?)\[/url\]").unwrap(),
310 "<a href=\"$2\" rel=\"nofollow\" target=\"_new\">$1</a>"),
311 (Regex::new(r"(?s)\[quote\](.*?)\[/quote\]").unwrap(),
313 "<div class=\"quote\">$1</div>"),
314 (Regex::new(r"(?s)\[quote=(.+)\](.*?)\[/quote\]").unwrap(),
315 "<div class=\"quote\"><strong>$1 wrote:</strong>\n$2</div>"),
316 (Regex::new(r"(?s)\[img=(\d+)x(\d+)(\b.*)?\](.*?)\[/img\]").unwrap(),
318 "<img src=\"$4\" width=\"$1\" height=\"$2\"$3 />"),
319 (Regex::new(r"(?s)\[img=(.+)(\b.*)?\](.*?)\[/img\]").unwrap(),
320 "<img src=\"$3\" alt=\"$1\"$2 />"),
321 (Regex::new(r"(?s)\[img(\b.*)?\](.*?)\[/img\]").unwrap(),
322 "<img src=\"$2\"$1 />"),
323 (Regex::new(r"(?s)\[ol\](.*?)\[/ol\]").unwrap(), "<ol>$1</ol>"),
325 (Regex::new(r"(?s)\[ul\](.*?)\[/ul\]").unwrap(), "<ul>$1</ul>"),
326 (Regex::new(r"(?s)\[list\](.*?)\[/list\]").unwrap(), "<ul>$1</ul>"),
327 (Regex::new(r"(?s)\[youtube\](.*?)\[/youtube\]").unwrap(),
329 "<object data=\"http://www.youtube.com/embed/$1\"></object>"),
330 (Regex::new(r"(?s)\[youtube=(\d+)x(\d+)\](.*?)\[/youtube\]").unwrap(),
331 "<object width=\"$1\" height=\"$2\" data=\"http://www.youtube.com/embed/$3\"></object>"),
332 (Regex::new(r"(?s)\[li\](.*?)\[/li\]").unwrap(), "<li>$1</li>"),
334 ];
335
336 }
337 &PATTERNS
338}
339
340pub fn str_to_html(input: &str) -> String {
341 let mut output = code(&input);
342 for &(ref pattern, replace) in patterns() {
343 output = pattern.replace_all(&output, replace).into_owned();
344 }
345 output
346}
347
348impl<'a> BBCode for &'a str {
349 fn as_html(&self) -> String {
350 str_to_html(&self)
351
352 }
353}