lazy_static! {
static ref BLOCK_ELEMENTS: Vec<&'static str> = vec![
"_root_",
"address",
"article",
"aside",
"blockquote",
"canvas",
"dd",
"div",
"dl",
"dt",
"fieldset",
"figcaption",
"figure",
"footer",
"form",
"h1",
"h2",
"h3",
"h4",
"h5",
"h6",
"header",
"hgroup",
"hr",
"li",
"main",
"nav",
"noscript",
"ol",
"output",
"p",
"pre",
"section",
"table",
"tfoot",
"ul",
"video",
];
static ref WRAPPER_ELEMENTS: Vec<&'static str> = vec![
"_root_",
"article",
"aside",
"blockquote",
"div",
"fieldset",
"footer",
"form",
"header",
"hgroup",
"main",
"section",
];
static ref SELF_CLOSING_ELEMENTS: Vec<&'static str> = vec![
"area", "base", "br", "col", "embed", "hr", "img", "input", "keygen", "link", "meta",
"param", "source", "track", "wbr",
];
static ref PREFORMATTED_ELEMENTS: Vec<&'static str> = vec!["pre"];
}
#[derive(Clone, Debug, PartialEq)]
pub struct ClassAttribute {
pub name: String,
}
impl ClassAttribute {
pub fn new(name: String) -> Data {
Data::ClassAttribute(ClassAttribute { name: name })
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct DataAttribute {
pub name: String,
pub children: Option<Vec<Data>>,
}
impl DataAttribute {
pub fn new(name: String, children: Option<Vec<Data>>) -> Data {
Data::DataAttribute(DataAttribute {
name: name,
children: children,
})
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct IdAttribute {
pub name: String,
}
impl IdAttribute {
pub fn new(name: String) -> Data {
Data::IdAttribute(IdAttribute { name: name })
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct NamedAttribute {
pub name: String,
pub children: Option<Vec<Data>>,
}
impl NamedAttribute {
pub fn new(name: String, children: Option<Vec<Data>>) -> Data {
Data::NamedAttribute(NamedAttribute {
name: name,
children: children,
})
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct Element {
pub name: String,
pub attributes: Option<Vec<Data>>,
pub children: Option<Vec<Data>>,
}
impl Element {
pub fn new(name: String, attributes: Option<Vec<Data>>, children: Option<Vec<Data>>) -> Data {
let mut element = Element {
name: name.to_string(),
attributes: attributes,
children: children,
};
if element.is_wrapper() {
element.wrap_paragraph_breaks();
if element.name == String::from("_root_") {
Data::RootElement(element.children)
} else {
Data::BlockElement(element)
}
} else if element.is_block() {
element.replace_paragraph_breaks();
element.cleanup_line_breaks();
Data::BlockElement(element)
} else {
element.replace_paragraph_breaks();
element.cleanup_line_breaks();
Data::InlineElement(element)
}
}
pub fn is_block(&self) -> bool {
BLOCK_ELEMENTS.contains(&self.name.as_str())
}
pub fn is_wrapper(&self) -> bool {
WRAPPER_ELEMENTS.contains(&self.name.as_str())
}
pub fn is_inline(&self) -> bool {
!self.is_block()
}
pub fn is_self_closing(&self) -> bool {
SELF_CLOSING_ELEMENTS.contains(&self.name.as_str())
}
pub fn is_preformatted(&self) -> bool {
PREFORMATTED_ELEMENTS.contains(&self.name.as_str())
}
pub fn wrap_paragraph_breaks(&mut self) {
if let Some(input) = self.children.to_owned() {
let mut output: Vec<Data> = Vec::new();
let mut paragraph: Vec<Data> = Vec::new();
let mut length = 0;
for child in input {
match child {
Data::ParagraphBreak(_) => {
if length > 0 {
let element = Element::new(String::from("p"), None, Some(paragraph));
paragraph = Vec::new();
length = 0;
if let Data::BlockElement(Element {
name: _,
attributes: _,
children: Some(_),
}) = &element
{
output.push(element);
}
}
}
Data::BlockElement(_) => {
if length > 0 {
let element = Element::new(String::from("p"), None, Some(paragraph));
paragraph = Vec::new();
length = 0;
if let Data::BlockElement(Element {
name: _,
attributes: _,
children: Some(_),
}) = &element
{
output.push(element);
}
}
output.push(child.to_owned());
}
_ => {
length += 1;
paragraph.push(child.to_owned());
}
}
}
if length > 0 {
let element = Element::new(String::from("p"), None, Some(paragraph));
if let Data::BlockElement(Element {
name: _,
attributes: _,
children: Some(_),
}) = &element
{
output.push(element);
}
}
self.children = Some(output);
}
}
pub fn replace_paragraph_breaks(&mut self) {
if let Some(input) = self.children.to_owned() {
let mut output: Vec<Data> = Vec::new();
for child in input {
match child {
Data::ParagraphBreak(size) => {
output.push(Data::LineBreak(size.to_owned()));
}
_ => {
output.push(child.to_owned());
}
}
}
self.children = Some(output);
}
}
pub fn cleanup_line_breaks(&mut self) {
if let Some(input) = self.children.to_owned() {
let mut before = 0;
let mut after = 0;
let mut middle = false;
for child in &input {
match child {
Data::LineBreak(_) => {
if middle {
after += 1;
} else {
before += 1;
}
}
_ => {
after = 0;
middle = true;
}
}
}
after = input.len() - after;
if before == after {
self.children = None;
} else if before < after {
self.children = Some(input[before..after].to_vec());
} else if after > 0 {
self.children = Some(input[0..after].to_vec());
}
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum Data {
RootElement(Option<Vec<Data>>),
BlockElement(Element),
InlineElement(Element),
ClassAttribute(ClassAttribute),
DataAttribute(DataAttribute),
IdAttribute(IdAttribute),
NamedAttribute(NamedAttribute),
Url(String),
Text(String),
Escape(String),
LineBreak(usize),
ParagraphBreak(usize),
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_class_attribute() {
let result = ClassAttribute::new("foobar".to_string());
assert_eq!(
result,
Data::ClassAttribute(ClassAttribute {
name: "foobar".to_string()
})
);
}
#[test]
fn test_data_attribute() {
let result = DataAttribute::new(
"foobar".to_string(),
Some(vec![Data::Text("boofar".to_string())]),
);
assert_eq!(
result,
Data::DataAttribute(DataAttribute {
name: "foobar".to_string(),
children: Some(vec![Data::Text("boofar".to_string())])
})
);
}
#[test]
fn test_id_attribute() {
let result = IdAttribute::new("foobar".to_string());
assert_eq!(
result,
Data::IdAttribute(IdAttribute {
name: "foobar".to_string(),
})
);
}
#[test]
fn test_named_attribute() {
let result = NamedAttribute::new(
"foobar".to_string(),
Some(vec![Data::Text("boofar".to_string())]),
);
assert_eq!(
result,
Data::NamedAttribute(NamedAttribute {
name: "foobar".to_string(),
children: Some(vec![Data::Text("boofar".to_string())])
})
);
}
#[test]
fn test_paragraph_wrapping() {
let result = Element::new(
"div".to_string(),
None,
Some(vec![Data::Text("boofar".to_string())]),
);
assert_eq!(
result,
Data::BlockElement(Element {
name: "div".to_string(),
attributes: None,
children: Some(vec![Data::BlockElement(Element {
name: "p".to_string(),
attributes: None,
children: Some(vec![Data::Text("boofar".to_string())])
})])
})
);
if let Data::BlockElement(element) = result {
assert!(element.is_block());
assert!(element.is_wrapper());
}
}
#[test]
fn test_block_element() {
let result = Element::new(
"p".to_string(),
None,
Some(vec![Data::Text("boofar".to_string())]),
);
assert_eq!(
result,
Data::BlockElement(Element {
name: "p".to_string(),
attributes: None,
children: Some(vec![Data::Text("boofar".to_string())]),
})
);
if let Data::BlockElement(element) = result {
assert!(element.is_block());
}
}
#[test]
fn test_inline_element() {
let result = Element::new(
"i".to_string(),
None,
Some(vec![Data::Text("boofar".to_string())]),
);
assert_eq!(
result,
Data::InlineElement(Element {
name: "i".to_string(),
attributes: None,
children: Some(vec![Data::Text("boofar".to_string())]),
})
);
if let Data::InlineElement(element) = result {
assert!(element.is_inline());
}
}
#[test]
fn test_preformatted_element() {
let result = Element::new(
"pre".to_string(),
None,
Some(vec![Data::Text("boofar".to_string())]),
);
assert_eq!(
result,
Data::BlockElement(Element {
name: "pre".to_string(),
attributes: None,
children: Some(vec![Data::Text("boofar".to_string())]),
})
);
if let Data::BlockElement(element) = result {
assert!(element.is_block());
assert!(element.is_preformatted());
}
}
}