use crate::state::SharedState;
use serde::Serialize;
use std::collections::HashMap;
pub type NodeAttributes = Option<HashMap<String, String>>;
pub type NodeChildren = Option<Vec<Node>>;
pub type NodeTags = Option<Vec<String>>;
pub type Nodes = Vec<Node>;
#[derive(Debug, Clone, PartialEq, Serialize)]
pub enum Node {
Fragment {
children: NodeChildren,
},
Element {
name: String,
attributes: NodeAttributes,
tags: NodeTags,
children: NodeChildren,
},
Span {
attributes: NodeAttributes,
tags: NodeTags,
children: NodeChildren,
},
Html {
html: String,
},
Text {
text: String,
},
Word {
word: String,
},
Placeholder {
id: String,
},
Whitespace {
space: String,
},
NonBreakingSpace {
space: String,
},
Removed,
}
impl Node {
pub(crate) fn finish<'a>(self, state: SharedState) -> Self {
fn finish_children(state: SharedState, children: NodeChildren) -> NodeChildren {
if let Some(children) = children {
let mut results = Vec::new();
for child in children {
results.push(child.finish(state.clone()));
}
Some(results)
} else {
None
}
}
fn finish_attributes(
state: SharedState,
mut attributes: NodeAttributes,
tags: NodeTags,
) -> NodeAttributes {
if let Some(tags) = tags {
for (key, tag_attributes) in state.borrow().tags.clone() {
if tags.contains(&key) {
if let Some(tag_attributes) = tag_attributes {
for (name, value) in tag_attributes {
match name.as_str() {
"class" => {
attributes = if let Some(mut attributes) = attributes {
attributes
.entry(name.to_owned())
.and_modify(|current| {
*current = format!("{} {}", current, value)
})
.or_insert(value.to_owned());
Some(attributes)
} else {
Some(vec![(name, value)].into_iter().collect())
}
}
_ => {
attributes = if let Some(mut attributes) = attributes {
attributes
.entry(name.to_owned())
.and_modify(|current| *current = value.to_owned())
.or_insert(value.to_owned());
Some(attributes)
} else {
Some(vec![(name, value)].into_iter().collect())
}
}
}
}
}
}
}
}
attributes
}
match self {
Node::Fragment { children } => {
let node = Node::Fragment {
children: finish_children(state.clone(), children),
};
node
}
Node::Element {
name,
attributes,
children,
tags,
} => {
let node = Node::Element {
name,
tags: None,
attributes: finish_attributes(state.clone(), attributes, tags),
children: finish_children(state.clone(), children),
};
node
}
Node::Span {
attributes,
children,
tags,
} => {
let node = Node::Span {
tags: None,
attributes: finish_attributes(state.clone(), attributes, tags),
children: finish_children(state.clone(), children),
};
node
}
Node::Placeholder { id } => {
Node::Placeholder { id }
}
_ => self,
}
}
pub fn with_tags(self, tags: Vec<String>) -> Self {
match self {
Node::Element {
name,
attributes,
children,
..
} => Node::Element {
tags: Some(tags),
name,
attributes,
children,
},
Node::Span {
attributes,
children,
..
} => Node::Span {
tags: Some(tags),
attributes,
children,
},
Node::Text { text } => Node::Span {
tags: Some(tags),
attributes: None,
children: Some(vec![Node::Text { text }]),
},
Node::Word { word } => Node::Span {
tags: Some(tags),
attributes: None,
children: Some(vec![Node::Word { word }]),
},
Node::Placeholder { id } => Node::Span {
tags: Some(tags),
attributes: None,
children: Some(vec![Node::Placeholder { id }]),
},
_ => self,
}
}
pub fn html(&self) -> String {
let mut result = String::new();
match self {
Node::Fragment { children } => {
result.push_str(&children.html());
}
Node::Element {
name,
attributes,
children,
..
} => {
result.push_str(&html_element(name, attributes, children));
}
Node::Span {
attributes,
children,
..
} => {
result.push_str(&html_element("span", attributes, children));
}
Node::Html { html } => {
result.push_str(&html);
}
Node::Text { text } => {
result.push_str(&text.replace('&', "&")
.replace('"', """)
.replace('<', "<")
.replace('>', ">"));
}
Node::Word { word } => {
result.push_str(&word.replace('&', "&")
.replace('"', """)
.replace('<', "<")
.replace('>', ">"));
}
Node::Whitespace { .. } => {
result.push_str(" ");
}
Node::NonBreakingSpace { .. } => {
result.push_str("Â ");
}
Node::Placeholder { .. } | Node::Removed => {}
}
result
}
pub fn text(&self) -> String {
let mut result = String::new();
match self {
Node::Fragment { children }
| Node::Element { children, .. }
| Node::Span { children, .. } => {
result.push_str(&children.text());
}
Node::Html { html } => {
result.push_str(html);
}
Node::Text { text } => {
result.push_str(text);
}
Node::Word { word } => {
result.push_str(word);
}
Node::Whitespace { .. } | Node::NonBreakingSpace { .. } => {
result.push_str(" ");
}
Node::Placeholder { .. } | Node::Removed => {}
};
result
}
}
pub trait RenderableNode {
fn html(&self) -> String;
fn text(&self) -> String;
}
impl RenderableNode for Vec<Node> {
fn html(&self) -> String {
let mut result = String::new();
for element in self {
result.push_str(&element.html());
}
result
}
fn text(&self) -> String {
let mut result = String::new();
for element in self {
result.push_str(&element.text());
}
result
}
}
impl RenderableNode for NodeAttributes {
fn html(&self) -> String {
let mut result = String::new();
if let Some(attributes) = &self {
for (key, value) in attributes {
result.push_str(&format!(
r#" {}="{}""#,
key,
value
.replace('&', "&")
.replace('"', """)
.replace('<', "<")
.replace('>', ">")
));
}
}
result
}
fn text(&self) -> String {
String::new()
}
}
impl RenderableNode for NodeChildren {
fn html(&self) -> String {
let mut result = String::new();
if let Some(elements) = &self {
for element in elements {
result.push_str(&element.html());
}
}
result
}
fn text(&self) -> String {
let mut result = String::new();
if let Some(elements) = &self {
for element in elements {
result.push_str(&element.text());
}
}
result
}
}
enum ElementType {
Text,
LinkedText,
Image,
LinkedImage,
}
fn html_element(name: &str, attributes: &NodeAttributes, elements: &NodeChildren) -> String {
let mut result = String::new();
match if let Some(attributes) = attributes {
if attributes.contains_key("href") && attributes.contains_key("src") {
ElementType::LinkedImage
} else if attributes.contains_key("src") {
ElementType::Image
} else if attributes.contains_key("href") {
ElementType::LinkedText
} else {
ElementType::Text
}
} else {
ElementType::Text
} {
ElementType::LinkedImage => {
let mut attributes = attributes.clone().unwrap();
let href = attributes.remove("href").unwrap();
result.push_str(&format!(
r#"<a href="{href}"><img{attributes} alt="{alt}"></a>"#,
alt = elements.text(),
attributes = (&Some(attributes)).html(),
href = href,
));
}
ElementType::Image => result.push_str(&format!(
r#"<img{attributes} alt="{alt}">"#,
alt = elements.text(),
attributes = attributes.html(),
)),
ElementType::LinkedText => result.push_str(&format!(
"<{name}><a{attributes}>{children}</a></{name}>",
name = name,
attributes = attributes.html(),
children = elements.html(),
)),
_ => result.push_str(&format!(
"<{name}{attributes}>{children}</{name}>",
name = name,
attributes = attributes.html(),
children = elements.html(),
)),
};
result
}