Skip to main content

bbx/html/
builtins.rs

1//! Built-in implementations of common (i.e. used by many implementations, subjectively) BBCode tags.
2use core::marker::PhantomData;
3
4use super::HtmlTagWriter;
5
6impl<T: SimpleHtmlTagWriter<CustomTy>, CustomTy> HtmlTagWriter<CustomTy> for T
7where
8    CustomTy: Clone + 'static,
9{
10    fn match_tag(&self, tag: &str) -> bool {
11        Self::TAGS.iter().any(|x| x.eq_ignore_ascii_case(tag))
12    }
13
14    fn open_tag(
15        &self,
16        tk_writer: &dyn super::HtmlTokenWriter<CustomTy>,
17        token: &crate::Token<'_, CustomTy>,
18        out: &mut String,
19    ) {
20        // TODO: This could use let expressions when those are stable late-bound.
21        if Self::TAGS.iter().any(|x| token.is_open_argless(x)) && Self::HTML_OPEN.is_some() {
22            out.push_str(Self::HTML_OPEN.unwrap())
23        } else {
24            tk_writer.write_token(token, out)
25        }
26    }
27
28    fn close_tag<'a>(
29        &self,
30        tk_writer: &dyn super::HtmlTokenWriter<CustomTy>,
31        _: &crate::Token<'a, CustomTy>,
32        close_token: &crate::Token<'a, CustomTy>,
33        out: &mut String,
34    ) {
35        if Self::TAGS.iter().any(|x| close_token.is_close_argless(x)) && Self::HTML_CLOSE.is_some()
36        {
37            out.push_str(Self::HTML_CLOSE.unwrap())
38        } else {
39            tk_writer.write_token(close_token, out)
40        }
41    }
42
43    fn standalone_tag(
44        &self,
45        tk_writer: &dyn super::HtmlTokenWriter<CustomTy>,
46        token: &crate::Token<'_, CustomTy>,
47        out: &mut String,
48    ) {
49        if Self::TAGS.iter().any(|x| token.is_standalone_argless(x))
50            && Self::HTML_STANDALONE.is_some()
51        {
52            out.push_str(Self::HTML_STANDALONE.unwrap())
53        } else {
54            tk_writer.write_token(token, out)
55        }
56    }
57}
58
59trait SimpleHtmlTagWriter<CustomTy>
60where
61    CustomTy: Clone,
62{
63    const TAGS: &'static [&'static str];
64
65    const HTML_OPEN: Option<&'static str>;
66
67    const HTML_CLOSE: Option<&'static str>;
68
69    const HTML_STANDALONE: Option<&'static str>;
70}
71
72macro_rules! simple_tag {
73    ($doc:expr, $name:ident, $tags:expr, $open:expr, $close:expr) => {
74        #[derive(Copy, Clone, Debug, Default)]
75        #[doc = $doc]
76        #[doc = "<br/>"]
77        #[doc = "This matches the following BBCode tags: `"]
78        #[doc = stringify!($tags)]
79        #[doc = "`"]
80        #[doc = "# Exact output"]
81        #[doc = "This tag converts exactly to"]
82        #[doc = "```html"]
83        #[doc = $open]
84        #[doc = " contents"]
85        #[doc = $close]
86        #[doc = "```"]
87        pub struct $name<CustomTy = ()> {
88            _custom_ty: PhantomData<CustomTy>,
89        }
90
91        impl<CustomTy> SimpleHtmlTagWriter<CustomTy> for $name<CustomTy>
92        where
93            CustomTy: Clone,
94        {
95            const TAGS: &'static [&'static str] = &$tags;
96            const HTML_CLOSE: Option<&'static str> = Some($close);
97            const HTML_OPEN: Option<&'static str> = Some($open);
98            const HTML_STANDALONE: Option<&'static str> = None;
99        }
100    };
101}
102
103macro_rules! simple_standalone_tag {
104    ($doc:expr, $name:ident, $tags:expr, $standalone:expr) => {
105        #[derive(Copy, Clone, Debug, Default)]
106        #[doc = $doc]
107        #[doc = "<br/>"]
108        #[doc = "This matches the following BBCode tags: `"]
109        #[doc = stringify!($tags)]
110        #[doc = "`"]
111        #[doc = "# Exact output"]
112        #[doc = "This tag converts exactly to"]
113        #[doc = "```html"]
114        #[doc = $standalone]
115        #[doc = "```"]
116        pub struct $name<CustomTy = ()> {
117            _custom_ty: PhantomData<CustomTy>,
118        }
119
120        impl<CustomTy> SimpleHtmlTagWriter<CustomTy> for $name<CustomTy>
121        where
122            CustomTy: Clone,
123        {
124            const TAGS: &'static [&'static str] = &$tags;
125            const HTML_CLOSE: Option<&'static str> = None;
126            const HTML_OPEN: Option<&'static str> = None;
127            const HTML_STANDALONE: Option<&'static str> = Some($standalone);
128        }
129    };
130}
131
132// "Safe" tags, as per the definition in all_core_v1_tags.
133simple_tag! {
134    "A bold tag with no arguments, which converts directly to HTML5 `<b>`.",
135    BoldTag, ["b", "bold"], "<b>", "</b>"
136}
137simple_tag! {
138    "An italic tag with no arguments, which converts directly to HTML5 `<i>`.",
139    ItalicTag, ["i", "italic"], "<i>", "</i>"
140}
141simple_tag! {
142    "An underline tag no arguments, which converts directly to HTML5 `<u>`.",
143    UnderlineTag, ["u", "underline", "under"], "<u>", "</u>"
144}
145simple_standalone_tag! {
146    "A linebreak tag with no arguments, which converts directly into HTML5 `<br/>`.",
147    LinebreakTag, ["br"], "<br/>"
148}
149simple_tag! {
150    "A block quote tag with no arguments, which converts directly to HTML5 `<blockquote>`.",
151    BlockQuoteTag, ["quote", "blockquote"], "<blockquote>", "</blockquote>"
152}
153simple_tag! {
154    "Inline quote tag with no arguments, which converts directly to HTML5 `<q>`.",
155    QuoteTag, ["q"], "<q>", "</q>"
156}
157simple_tag! {
158    "Subscript tag with no arguments, which converts directly to HTML5 `<sub>`.",
159    SubscriptTag, ["sub", "subscript", "small"], "<sub>", "</sub>"
160}
161simple_tag! {
162    "Superscript tag with no arguments, which converts directly to HTML5 `<sup>`.",
163    SuperscriptTag, ["sup", "super", "superscript"], "<sup>", "</sup>"
164}
165simple_tag! {
166    "Header (tier 1) tag with no arguments, which converts directly to HTML5 `<h1>`.",
167    Header1Tag, ["h1", "title"], "<h1>", "</h1>"
168}
169simple_tag! {
170    "Header (tier 2) tag with no arguments, which converts directly to HTML5 `<h2>`.",
171    Header2Tag, ["h2", "topic"], "<h2>", "</h2>"
172}
173simple_tag! {
174    "Header (tier 3) tag with no arguments, which converts directly to HTML5 `<h3>`.",
175    Header3Tag, ["h3", "subtopic"], "<h3>", "</h3>"
176}
177simple_tag! {
178    "Header (tier 4) tag with no arguments, which converts directly to HTML5 `<h4>`.",
179    Header4Tag, ["h4"], "<h4>", "</h4>"
180}
181simple_tag! {
182    "Header (tier 5) tag with no arguments, which converts directly to HTML5 `<h5>`.",
183    Header5Tag, ["h5"], "<h5>", "</h5>"
184}
185simple_tag! {
186    "Header (tier 6) tag with no arguments, which converts directly to HTML5 `<h6>`.",
187    Header6Tag, ["h6"], "<h6>", "</h6>"
188}
189simple_tag! {
190    "Centering tag with no arguments, which converts to a div with styling to horizontally center it.",
191    CenterTag, ["center"], "<div style=\"display: flex; justify-content: center;\"><div>", "</div></div>"
192}
193simple_tag! {
194    "Left-align tag with no arguments, which converts to a div with styling to left-align it.",
195    LeftTag, ["left"], "<div style=\"display: flex; justify-content: left;\"><div>", "</div></div>"
196}
197simple_tag! {
198    "Right-align tag with no arguments, which converts to a div with styling to right-align it.",
199    RightTag, ["right"], "<div style=\"display: flex; justify-content: right;\"><div>", "</div></div>"
200}
201simple_tag! {
202    "Preformatted styling tag with no arguments, which converts directly to HTML5 `<pre>`.",
203    PreformattedTag, ["pre", "codeblock"], "<pre>", "</pre>"
204}
205simple_tag! {
206    "Code styling tag with no arguments, which converts directly to HTML5 `<code>`.",
207    CodeTag, ["code"], "<code>", "</code>"
208}
209simple_tag! {
210    "Keypress styling tag with no arguments, which converts directly to HTML5 `<kbd>`.",
211    KbdTag, ["kbd"], "<kbd>", "</kbd>"
212}
213
214macro_rules! tag_list {
215    ($ct:ident; $($tag:ident),*) => {
216        {
217            let v: Vec<Box<dyn HtmlTagWriter<$ct>>> = vec![
218                $(
219                    Box::new($tag::default()),
220                )*
221            ];
222
223            v
224        }
225    };
226}
227
228/// Returns all built-in tags from v1.0.0 (or earlier) of the library that are considered "basic" and safe for all usages by the authors.
229/// # "Safe"
230/// Safe, in the context of this list, is defined as 100% no doubts safe for using from random user input. This precludes things like image embeds, complex formatting, and links, which can be unsafe in some contexts and require special parsing.
231/// # Included tags
232/// - [BoldTag]
233/// - [ItalicTag]
234/// - [UnderlineTag]
235/// - [LinebreakTag]
236/// - [QuoteTag]
237/// - [BlockQuoteTag]
238/// - [SubscriptTag]
239/// - [SuperscriptTag]
240/// - [Header1Tag]
241/// - [Header2Tag]
242/// - [Header3Tag]
243/// - [Header4Tag]
244/// - [Header5Tag]
245/// - [Header6Tag]
246/// - [CenterTag]
247/// - [CodeTag]
248/// - [PreformattedTag]
249/// - [KbdTag]
250pub fn all_core_v1_tags<CustomTy>() -> Vec<Box<dyn HtmlTagWriter<CustomTy>>>
251where
252    CustomTy: Clone + Default + 'static,
253{
254    tag_list! {CustomTy;
255        BoldTag,
256        ItalicTag,
257        UnderlineTag,
258        LinebreakTag,
259        QuoteTag,
260        BlockQuoteTag,
261        SubscriptTag,
262        SuperscriptTag,
263        Header1Tag,
264        Header2Tag,
265        Header3Tag,
266        Header4Tag,
267        Header5Tag,
268        Header6Tag,
269        CenterTag
270    }
271}