bbcode/
lib.rs

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>&#91;b&#93;test&#91;/b&#93;</code></pre>")
171    }
172
173    #[test]
174    fn preformatted() {
175        assert_eq!("[preformatted][b]test[/b][/preformatted]".as_html(),
176                   "<pre><code>&#91;b&#93;test&#91;/b&#93;</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
245/// BBCode is a trait that will convert the input BBCode into HTML
246///
247/// Included in this is a default ipml for &str, allowing you to
248///
249/// ```
250/// use bbcode::BBCode;
251///
252/// assert_eq!("[b]Bold![/b]".as_html(), "<strong>Bold!</strong>");
253/// ```
254pub 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 [("[", "&#91;"), ("]", "&#93;"), ("<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          // Font changes
286          (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          // Alignment
295          (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          // Tables
302          (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          // Links
307          (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          // Quotes
312          (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          // Images
317          (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          // Lists
324          (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          // Youtube
328          (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          // List Items
333          (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}