Content: Fix closing tag lookahead, support [br]
This commit is contained in:
parent
84d5aa39cf
commit
f2552c794d
|
@ -32,6 +32,7 @@ pub enum Tag {
|
||||||
IconText,
|
IconText,
|
||||||
Icon,
|
Icon,
|
||||||
Hr,
|
Hr,
|
||||||
|
Br,
|
||||||
Url,
|
Url,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,7 +69,7 @@ struct ClosingTagBackout;
|
||||||
|
|
||||||
impl Tag {
|
impl Tag {
|
||||||
fn needs_closing(&self) -> bool {
|
fn needs_closing(&self) -> bool {
|
||||||
!matches!(self, Tag::Hr)
|
!matches!(self, Tag::Hr) && !matches!(self, Tag::Br)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn with_attribute(&self, attribute: &Option<String>) -> Option<String> {
|
fn with_attribute(&self, attribute: &Option<String>) -> Option<String> {
|
||||||
|
@ -121,6 +122,7 @@ where
|
||||||
"icontext" => Tag::IconText,
|
"icontext" => Tag::IconText,
|
||||||
"icon" => Tag::Icon,
|
"icon" => Tag::Icon,
|
||||||
"hr" => Tag::Hr,
|
"hr" => Tag::Hr,
|
||||||
|
"br" => Tag::Br,
|
||||||
"url" => Tag::Url,
|
"url" => Tag::Url,
|
||||||
_ => {
|
_ => {
|
||||||
return Err(StreamErrorFor::<Input>::other(TagError(
|
return Err(StreamErrorFor::<Input>::other(TagError(
|
||||||
|
@ -135,6 +137,19 @@ where
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn peek_closing_tag<Input>(tag: Tag) -> impl Parser<Input, Output = ()>
|
||||||
|
where
|
||||||
|
Input: Stream<Token = char>,
|
||||||
|
{
|
||||||
|
look_ahead(between(token('['), token(']'), tag_string())).and_then(move |closing_tag| {
|
||||||
|
if closing_tag == format!("/{}", tag) {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(StreamErrorFor::<Input>::other(TagError(closing_tag)))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn closing_tag<Input>(tag: Tag) -> impl Parser<Input, Output = ()>
|
fn closing_tag<Input>(tag: Tag) -> impl Parser<Input, Output = ()>
|
||||||
where
|
where
|
||||||
Input: Stream<Token = char>,
|
Input: Stream<Token = char>,
|
||||||
|
@ -273,25 +288,11 @@ where
|
||||||
satisfy(|c| c != '\n')
|
satisfy(|c| c != '\n')
|
||||||
}
|
}
|
||||||
|
|
||||||
fn char_node<Input>(closing: Option<Tag>) -> impl Parser<Input, Output = Node>
|
fn char_node<Input>() -> impl Parser<Input, Output = Node>
|
||||||
where
|
where
|
||||||
Input: Stream<Token = char>,
|
Input: Stream<Token = char>,
|
||||||
{
|
{
|
||||||
if let Some(tag) = closing {
|
valid_char().map(|text| Node::CharNode { text })
|
||||||
look_ahead(closing_tag(tag))
|
|
||||||
.map(|_| None)
|
|
||||||
.or(valid_char().map(Some))
|
|
||||||
.and_then(|text: Option<char>| {
|
|
||||||
if let Some(text) = text {
|
|
||||||
Ok(Node::CharNode { text })
|
|
||||||
} else {
|
|
||||||
Err(StreamErrorFor::<Input>::other(ClosingTagBackout))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.left()
|
|
||||||
} else {
|
|
||||||
valid_char().map(|text| Node::CharNode { text }).right()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn newline_node<Input>() -> impl Parser<Input, Output = Node>
|
fn newline_node<Input>() -> impl Parser<Input, Output = Node>
|
||||||
|
@ -301,7 +302,7 @@ where
|
||||||
many1(combine::parser::char::char('\n')).map(|_: String| Node::NewlineNode)
|
many1(combine::parser::char::char('\n')).map(|_: String| Node::NewlineNode)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn single_node<Input>(closing: Option<Tag>) -> impl Parser<Input, Output = Node>
|
fn single_node_<Input>() -> impl Parser<Input, Output = Node>
|
||||||
where
|
where
|
||||||
Input: Stream<Token = char>,
|
Input: Stream<Token = char>,
|
||||||
{
|
{
|
||||||
|
@ -310,11 +311,32 @@ where
|
||||||
attempt(handle_node()),
|
attempt(handle_node()),
|
||||||
attempt(email_node()),
|
attempt(email_node()),
|
||||||
attempt(url_node()),
|
attempt(url_node()),
|
||||||
char_node(closing),
|
char_node(),
|
||||||
newline_node(),
|
attempt(newline_node()),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn single_node<Input>(closing: Option<Tag>) -> impl Parser<Input, Output = Node>
|
||||||
|
where
|
||||||
|
Input: Stream<Token = char>,
|
||||||
|
{
|
||||||
|
if let Some(tag) = closing {
|
||||||
|
peek_closing_tag(tag)
|
||||||
|
.map(|_| None)
|
||||||
|
.or(single_node_().map(Some))
|
||||||
|
.and_then(move |node: Option<Node>| {
|
||||||
|
if let Some(node) = node {
|
||||||
|
Ok(node)
|
||||||
|
} else {
|
||||||
|
Err(StreamErrorFor::<Input>::other(ClosingTagBackout))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.left()
|
||||||
|
} else {
|
||||||
|
single_node_().right()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn node_vec_<Input>(closing: Option<Tag>) -> impl Parser<Input, Output = Vec<Node>>
|
fn node_vec_<Input>(closing: Option<Tag>) -> impl Parser<Input, Output = Vec<Node>>
|
||||||
where
|
where
|
||||||
Input: Stream<Token = char>,
|
Input: Stream<Token = char>,
|
||||||
|
@ -354,6 +376,7 @@ impl std::fmt::Display for Tag {
|
||||||
Tag::IconText => "icontext",
|
Tag::IconText => "icontext",
|
||||||
Tag::Icon => "icon",
|
Tag::Icon => "icon",
|
||||||
Tag::Hr => "hr",
|
Tag::Hr => "hr",
|
||||||
|
Tag::Br => "br",
|
||||||
Tag::Url => "url",
|
Tag::Url => "url",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -399,6 +422,36 @@ mod tests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_node_with_bad_inner_tag() {
|
||||||
|
let (node, rest) = node_vec(None)
|
||||||
|
.easy_parse("[center][bold][/center]")
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(rest, "");
|
||||||
|
match &node[0] {
|
||||||
|
Node::TagNode { tag, children, .. } => {
|
||||||
|
assert_eq!(*tag, Tag::Center);
|
||||||
|
assert_eq!(children.len(), 6);
|
||||||
|
}
|
||||||
|
_ => panic!("Invalid node type"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_node_with_unknown_inner_tag() {
|
||||||
|
let (node, rest) = node_vec(None)
|
||||||
|
.easy_parse("[center][unknown][/unknown][/center]")
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(rest, "");
|
||||||
|
match &node[0] {
|
||||||
|
Node::TagNode { tag, children, .. } => {
|
||||||
|
assert_eq!(*tag, Tag::Center);
|
||||||
|
assert_eq!(children.len(), 19);
|
||||||
|
}
|
||||||
|
_ => panic!("Invalid node type"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_multiple_nodes() {
|
fn parse_multiple_nodes() {
|
||||||
let (vec, rest) = node_vec(None)
|
let (vec, rest) = node_vec(None)
|
||||||
|
|
|
@ -73,6 +73,7 @@ static AMMONIA_CONFIG: Lazy<Builder> = Lazy::new(|| {
|
||||||
let div_hs = classes.entry("div").or_insert(HashSet::new());
|
let div_hs = classes.entry("div").or_insert(HashSet::new());
|
||||||
div_hs.insert("center");
|
div_hs.insert("center");
|
||||||
div_hs.insert("right");
|
div_hs.insert("right");
|
||||||
|
div_hs.insert("toolkit-code");
|
||||||
|
|
||||||
let span_hs = classes.entry("span").or_insert(HashSet::new());
|
let span_hs = classes.entry("span").or_insert(HashSet::new());
|
||||||
span_hs.insert("underline");
|
span_hs.insert("underline");
|
||||||
|
@ -82,6 +83,7 @@ static AMMONIA_CONFIG: Lazy<Builder> = Lazy::new(|| {
|
||||||
|
|
||||||
let pre_hs = classes.entry("pre").or_insert(HashSet::new());
|
let pre_hs = classes.entry("pre").or_insert(HashSet::new());
|
||||||
pre_hs.insert("codeblock");
|
pre_hs.insert("codeblock");
|
||||||
|
pre_hs.insert("toolkit-code--pre");
|
||||||
|
|
||||||
let mut schemes = HashSet::new();
|
let mut schemes = HashSet::new();
|
||||||
schemes.insert("http");
|
schemes.insert("http");
|
||||||
|
@ -104,6 +106,7 @@ static AMMONIA_CONFIG: Lazy<Builder> = Lazy::new(|| {
|
||||||
tags.insert("a");
|
tags.insert("a");
|
||||||
tags.insert("img");
|
tags.insert("img");
|
||||||
tags.insert("br");
|
tags.insert("br");
|
||||||
|
tags.insert("hr");
|
||||||
|
|
||||||
let mut builder = Builder::new();
|
let mut builder = Builder::new();
|
||||||
builder
|
builder
|
||||||
|
|
|
@ -95,8 +95,29 @@ enum RenderNode {
|
||||||
Newline,
|
Newline,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn trim(mut nodes: Vec<RenderNode>) -> Vec<RenderNode> {
|
||||||
|
let mut pop_last = false;
|
||||||
|
let mut pop_first = false;
|
||||||
|
if let Some(last) = nodes.last() {
|
||||||
|
pop_last = matches!(last, RenderNode::Newline);
|
||||||
|
}
|
||||||
|
if let Some(first) = nodes.first() {
|
||||||
|
pop_first = matches!(first, RenderNode::Newline);
|
||||||
|
}
|
||||||
|
|
||||||
|
if pop_last {
|
||||||
|
nodes.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
if pop_first {
|
||||||
|
nodes.into_iter().skip(1).collect()
|
||||||
|
} else {
|
||||||
|
nodes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn render_nodes(nodes: Vec<RenderNode>) -> String {
|
fn render_nodes(nodes: Vec<RenderNode>) -> String {
|
||||||
nodes
|
trim(nodes)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|node| {
|
.map(|node| {
|
||||||
log::trace!("Rendering {:?}", node);
|
log::trace!("Rendering {:?}", node);
|
||||||
|
@ -132,9 +153,9 @@ fn render_nodes(nodes: Vec<RenderNode>) -> String {
|
||||||
}
|
}
|
||||||
Tag::Codeblock if !children.is_empty() => {
|
Tag::Codeblock if !children.is_empty() => {
|
||||||
String::new()
|
String::new()
|
||||||
+ "<pre class=\"codeblock\">"
|
+ "<div class=\"toolkit-code\"><pre class=\"toolkit-code--pre\">"
|
||||||
+ &render_nodes(children)
|
+ &render_nodes(children)
|
||||||
+ "</pre>"
|
+ "</pre></div>"
|
||||||
}
|
}
|
||||||
Tag::Pre if !children.is_empty() => {
|
Tag::Pre if !children.is_empty() => {
|
||||||
String::new() + "<pre>" + &render_nodes(children) + "</pre>"
|
String::new() + "<pre>" + &render_nodes(children) + "</pre>"
|
||||||
|
@ -187,6 +208,7 @@ fn render_nodes(nodes: Vec<RenderNode>) -> String {
|
||||||
Tag::IconText if !children.is_empty() => render_nodes(children),
|
Tag::IconText if !children.is_empty() => render_nodes(children),
|
||||||
Tag::Icon if !children.is_empty() => render_nodes(children),
|
Tag::Icon if !children.is_empty() => render_nodes(children),
|
||||||
Tag::Hr => String::from("<hr>"),
|
Tag::Hr => String::from("<hr>"),
|
||||||
|
Tag::Br => String::from("<br>"),
|
||||||
Tag::Url if !children.is_empty() => {
|
Tag::Url if !children.is_empty() => {
|
||||||
if let Some(href) = attr {
|
if let Some(href) = attr {
|
||||||
format!("<a href=\"{}\" rel=\"noopener noreferer nofollow\">", href)
|
format!("<a href=\"{}\" rel=\"noopener noreferer nofollow\">", href)
|
||||||
|
@ -390,7 +412,6 @@ fn build_nodes(input: Vec<crate::bbcode::Node>) -> Vec<Node> {
|
||||||
let mut nodes = vec![];
|
let mut nodes = vec![];
|
||||||
|
|
||||||
for n in input {
|
for n in input {
|
||||||
log::trace!("Building {:?}", n);
|
|
||||||
match n {
|
match n {
|
||||||
crate::bbcode::Node::TagNode {
|
crate::bbcode::Node::TagNode {
|
||||||
tag,
|
tag,
|
||||||
|
|
Loading…
Reference in a new issue