use html5ever::{local_name, namespace_url, ns, LocalName, QualName}; use kuchiki::{iter::NodeEdge, traits::*, NodeData}; use super::{MessageComponent, MessageContent}; pub fn convert_matrix(message: &str) -> MessageContent { let dom = kuchiki::parse_fragment( QualName::new(None, ns!(html), LocalName::from("div")), vec![], ) .one(message); let mut parents = vec![]; let mut components = vec![]; for edge in dom.traverse() { match edge { NodeEdge::Start(node) => { if let NodeData::Element(element) = node.data() { if element.name.ns == ns!(html) { match element.name.local { local_name!("strong") | local_name!("b") | local_name!("em") | local_name!("i") => { parents.push(components); components = vec![]; } _ => {} } } } } NodeEdge::End(node) => { match node.data() { NodeData::Text(text) => { // TODO: Does this need to get HTML-decoded or something components.push(MessageComponent::Plain(text.borrow().clone())); } NodeData::Element(element) => { macro_rules! construct_component { ($f:expr) => {{ let component_type = $f; if let Some(mut parent_components) = parents.pop() { parent_components.push((component_type)(components)); components = parent_components; } }}; } if element.name.ns == ns!(html) { match element.name.local { local_name!("strong") | local_name!("b") => { construct_component!(MessageComponent::Bold) } local_name!("i") | local_name!("i") => { construct_component!(MessageComponent::Italic) } _ => {} } } } _ => {} } } }; } components } pub fn format_matrix(message_content: &[MessageComponent]) -> String { message_content .iter() .map(|component| match component { MessageComponent::Plain(text) => html_escape::encode_text(text).to_string(), MessageComponent::Link { target, text } => format!( r#"{}"#, html_escape::encode_quoted_attribute(target), format_matrix(text) ), MessageComponent::Italic(inner) => format!("{}", format_matrix(inner)), MessageComponent::Bold(inner) => format!("{}", format_matrix(inner)), MessageComponent::Strikethrough(inner) => { format!("{}", format_matrix(inner)) } MessageComponent::Underline(inner) => format!("{}", format_matrix(inner)), MessageComponent::Code(code) => { format!("{}", html_escape::encode_text(code)) } MessageComponent::CodeBlock { lang, source } => { format!( r#"
{}
"#, lang.as_ref() .map(|lang| format!( r#" class="language-{}""#, html_escape::encode_quoted_attribute(lang) )) .unwrap_or_else(|| "".to_string()), source, ) } MessageComponent::Spoiler { reason, content } => format!( "{}", reason .as_ref() .map(|reason| format!(r#"="{}""#, html_escape::encode_quoted_attribute(reason))) .unwrap_or_else(|| "".to_string()), format_matrix(content) ), MessageComponent::HardBreak => "
".to_string(), MessageComponent::BlockQuote(inner) => { format!("
{}
", format_matrix(inner)) } }) .collect() } #[test] fn simple_matrix_parsing() { let html = r"hello! <>"; let ast = convert_matrix(html); assert_eq!( ast, vec![MessageComponent::Bold(vec![ MessageComponent::Plain("hello! ".to_string(),), MessageComponent::Italic(vec![MessageComponent::Plain("<>".to_string())]) ])] ) }