Crates.io | bbscope |
lib.rs | bbscope |
version | 0.2.0 |
source | src |
created_at | 2022-12-13 14:42:49.453937 |
updated_at | 2023-07-05 19:57:13.307356 |
description | BBCode to HTML with scoping rules, auto-close tags, highly extensible |
homepage | |
repository | https://github.com/randomouscrap98/bbscope-rust |
max_upload_size | |
id | 735872 |
size | 58,914 |
Yeah! I needed something highly extensible, flexible, and specifically WITH scoping rules so it always produces correct HTML. For instance, there's stuff like:
[b]This is bold! [i]AND ITALIC?[/b] oops where's [/i]?
Where you have unmatching closing tags. While a simple regex replacement may handle this any number of ways, this library will produce:
<b>This is bold! <i>AND ITALIC?</i></b> oops where's ?
Another example:
And so [s] I'm just like [s] opening tons of [sup] tags?
Maybe I'll close this [/s] one
And so <s> I'm just like <s> opening tons of <sup> tags?<br>Maybe I'll close this </sup></s> one</s>
All unclosed tags are automatically closed in the correct order, including any that were left at the end of the text. Unmatched closing tags are removed. Of course, this may not be what you want, but I've found that most older or established bbcode parsers work this way. With this library, you can feel (generally) safe knowing it will produce proper HTML.
With scoping rules, you also get access to tags which can reject other tags inside of them,
by specifying the only
vector (more later). For instance, in the extended tagset, I have
[code] which rejects all types of matches except normal text and "garbage" (characters we
throw away, like \r).
let bbcode = BBCode::default().unwrap(); // Or catch error
let html = bbcode.parse("[url]https://github.com[/url]")
// Make sure to reuse the BBCode you created! Creating is expensive, but cloning is cheap!
let bbcode2 = bbcode.clone();
Or, if you want the extended set (see next section for list):
// Instead of requesting the default BBCode parser, you can pass in a configuration
// for the most common alterations, along with a vector of extra tag parsers (see later)
let mut bbcode = BBCode::from_config(BBCodeTagConfig::extended(), None).unwrap();
let html = bbcode.parse("[code]wow\nthis is code![/code]");
Or, if you want to add your own tags:
let mut config = BBCodeTagConfig::default(); // Default does not include extended tags fyi
let mut matchers = Vec<MatchInfo>::new(); // A list of NEW matchers we'll pass to from_config
// You define how your tag gets turned into HTML using a closure; you are provided the open tag
// regex capture, the pre-parsed pre-escaped body, and the closing tag regex capture (if the user provided it).
// "EmitScope" is just a fancy alias so you don't have to fuss with the complicated types
let color_emitter : EmitScope = Arc::new(|open_capture,body,_c| {
//NOTE: in production code, don't `unwrap` the named capture group, it might not exist!
let color = open_capture.unwrap().name("attr").unwrap().as_str();
format!(r#"<span style="color:{}">{}</span>"#, color, body)
});
BBCode::add_tagmatcher(&mut matchers, "color", ScopeInfo::basic(color_emitter), None, None)?;
//Repeat the emitter / add_tagmatcher above for each tag you want to add
let bbcode = BBCode::from_config(config, Some(matchers));
The BBCode::add_tagmatcher
method constructs a bbcode tag parser for you using less code than
normally required, but you can technically construct your own matcher manually which can match
almost anything. For now, if you're just trying to add basic bbcode tags, you'll see in the above:
EmitScope
, which is that closure you wrote that gives you
the regex capture for the open tag, the pre-html-escaped pre-parsed body, and the closing tag
regex capture, which you can use to output (emit) the constructed html. Note that, although the
opening tag is nearly always given, the closing tag is OFTEN not given, especially if the user
did not close their tags. Do not rely on the last parameter (_c
in the example) existingattr
, which is the value of the
attribute given in the bbcode tag. For instance, if you had [url=http://whatever]abc[/url]
,
the match attr
would house the string http://whatever
(NOT pre-escaped, be careful!)Some((1,0)), Some((0,1))
(this may change in the future)There are many web frameworks to choose from for rust, so having an example for each would be a bit difficult. Someone suggested Rocket, so here's an example in 0.5.0_rc2:
#[macro_use] extern crate rocket;
use rocket::response::content;
use bbscope::BBCode;
#[launch]
fn rocket() -> _ {
let bbcode = BBCode::default().unwrap();
rocket::build()
.mount("/", routes![ index ])
.manage(bbcode) //Add as state, you want to reuse the bbcode object!!
}
#[get("/")]
fn index(bbcode: &State<BBCode>) -> content::RawHtml<String> {
content::RawHtml(bbcode.parse("Hey, it's [b]bbcode[/b]! [i]Oops, [u]forgot to close[/i] a tag"))
}
BBCode is so varied, there's so many crazy tags and systems and nobody was ever able to agree on any, or at least if they did, it was too late. These are the tags supported in the 'basic' set:
Some of those may be nonstandard, and you may be missing some you find standard! If so, there's also an optional extended list:
And of course, the usual HTML characters are escaped everywhere: ', ", &, <, >
URLs not inside a url or img tag are auto-linked, or at least a best attempt is made at autolinking them (your mileage may vary)
BBCode::from_config(config, None)
and pass in a config with newline_to_br
set
to falseperf
feature (enables some regex optimizations, about a 4x improvement in my
testing)BBCode::default()
, or BBCode::basics()
and BBCode::extras()
,
it should still compatible, but if you were creating custom tags at all, the entire system was
scrapped in favor of the ScopeInfo
and EmitScope
combo[tag tag=attribute]