//! HTML syntax tree: [hast][]. //! //! [hast]: https://github.com/syntax-tree/hast #![allow(dead_code)] // ^-- To do: externalize. extern crate alloc; use alloc::{ fmt, string::{String, ToString}, vec::Vec, }; pub use markdown::mdast::{AttributeContent, AttributeValue, MdxJsxAttribute, Stop}; use markdown::unist::Position; /// Nodes. #[derive(Clone, PartialEq, Eq)] pub enum Node { /// Root. Root(Root), /// Element. Element(Element), /// Document type. Doctype(Doctype), /// Comment. Comment(Comment), /// Text. Text(Text), // MDX being passed through. /// MDX: JSX element. MdxJsxElement(MdxJsxElement), /// MDX.js ESM. MdxjsEsm(MdxjsEsm), // MDX: expression. MdxExpression(MdxExpression), } impl fmt::Debug for Node { // Debug the wrapped struct. fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Node::Root(x) => write!(f, "{:?}", x), Node::Element(x) => write!(f, "{:?}", x), Node::Doctype(x) => write!(f, "{:?}", x), Node::Comment(x) => write!(f, "{:?}", x), Node::Text(x) => write!(f, "{:?}", x), Node::MdxJsxElement(x) => write!(f, "{:?}", x), Node::MdxExpression(x) => write!(f, "{:?}", x), Node::MdxjsEsm(x) => write!(f, "{:?}", x), } } } fn children_to_string(children: &[Node]) -> String { children.iter().map(ToString::to_string).collect() } impl ToString for Node { fn to_string(&self) -> String { match self { // Parents. Node::Root(x) => children_to_string(&x.children), Node::Element(x) => children_to_string(&x.children), Node::MdxJsxElement(x) => children_to_string(&x.children), // Literals. Node::Comment(x) => x.value.clone(), Node::Text(x) => x.value.clone(), Node::MdxExpression(x) => x.value.clone(), Node::MdxjsEsm(x) => x.value.clone(), // Voids. Node::Doctype(_) => "".into(), } } } impl Node { #[must_use] pub fn children(&self) -> Option<&Vec> { match self { // Parent. Node::Root(x) => Some(&x.children), Node::Element(x) => Some(&x.children), Node::MdxJsxElement(x) => Some(&x.children), // Non-parent. _ => None, } } pub fn children_mut(&mut self) -> Option<&mut Vec> { match self { // Parent. Node::Root(x) => Some(&mut x.children), Node::Element(x) => Some(&mut x.children), Node::MdxJsxElement(x) => Some(&mut x.children), // Non-parent. _ => None, } } pub fn position(&self) -> Option<&Position> { match self { Node::Root(x) => x.position.as_ref(), Node::Element(x) => x.position.as_ref(), Node::Doctype(x) => x.position.as_ref(), Node::Comment(x) => x.position.as_ref(), Node::Text(x) => x.position.as_ref(), Node::MdxJsxElement(x) => x.position.as_ref(), Node::MdxExpression(x) => x.position.as_ref(), Node::MdxjsEsm(x) => x.position.as_ref(), } } pub fn position_mut(&mut self) -> Option<&mut Position> { match self { Node::Root(x) => x.position.as_mut(), Node::Element(x) => x.position.as_mut(), Node::Doctype(x) => x.position.as_mut(), Node::Comment(x) => x.position.as_mut(), Node::Text(x) => x.position.as_mut(), Node::MdxJsxElement(x) => x.position.as_mut(), Node::MdxExpression(x) => x.position.as_mut(), Node::MdxjsEsm(x) => x.position.as_mut(), } } pub fn position_set(&mut self, position: Option) { match self { Node::Root(x) => x.position = position, Node::Element(x) => x.position = position, Node::Doctype(x) => x.position = position, Node::Comment(x) => x.position = position, Node::Text(x) => x.position = position, Node::MdxJsxElement(x) => x.position = position, Node::MdxExpression(x) => x.position = position, Node::MdxjsEsm(x) => x.position = position, } } } /// Document. /// /// ```html /// > | a /// ^ /// ``` #[derive(Clone, Debug, PartialEq, Eq)] pub struct Root { // Parent. /// Content model. pub children: Vec, /// Positional info. pub position: Option, } /// Document type. /// /// ```html /// > | /// ^^^^^^^^^^^^^^^ /// ``` #[derive(Clone, Debug, PartialEq, Eq)] pub struct Element { pub tag_name: String, pub properties: Vec<(String, PropertyValue)>, // Parent. pub children: Vec, /// Positional info. pub position: Option, } #[derive(Clone, Debug, PartialEq, Eq)] pub enum PropertyValue { Boolean(bool), String(String), CommaSeparated(Vec), SpaceSeparated(Vec), } /// Document type. /// /// ```html /// > | /// ^^^^^^^^^^^^^^^ /// ``` #[derive(Clone, Debug, PartialEq, Eq)] pub struct Doctype { // Void. /// Positional info. pub position: Option, } /// Comment. /// /// ```html /// > | /// ^^^^^^^^^^ /// ``` #[derive(Clone, Debug, PartialEq, Eq)] pub struct Comment { // Text. /// Content model. pub value: String, /// Positional info. pub position: Option, } /// Text. /// /// ```html /// > | a /// ^ /// ``` #[derive(Clone, Debug, PartialEq, Eq)] pub struct Text { // Text. /// Content model. pub value: String, /// Positional info. pub position: Option, } /// MDX: JSX element. /// /// ```markdown /// > | /// ^^^^^ /// ``` #[derive(Clone, Debug, PartialEq, Eq)] pub struct MdxJsxElement { // JSX element. /// Name. /// /// Fragments have no name. pub name: Option, /// Attributes. pub attributes: Vec, // Parent. /// Content model. pub children: Vec, /// Positional info. pub position: Option, } /// MDX: expression. /// /// ```markdown /// > | {a} /// ^^^ /// ``` #[derive(Clone, Debug, PartialEq, Eq)] pub struct MdxExpression { // Literal. /// Content model. pub value: String, /// Positional info. pub position: Option, // Custom data on where each slice of `value` came from. pub stops: Vec, } /// MDX: ESM. /// /// ```markdown /// > | import a from 'b' /// ^^^^^^^^^^^^^^^^^ /// ``` #[derive(Clone, Debug, PartialEq, Eq)] pub struct MdxjsEsm { // Literal. /// Content model. pub value: String, /// Positional info. pub position: Option, // Custom data on where each slice of `value` came from. pub stops: Vec, } #[cfg(test)] mod tests { use super::*; use alloc::{format, string::ToString, vec}; use markdown::unist::Position; // Literals. #[test] fn text() { let mut node = Node::Text(Text { value: "a".into(), position: None, }); assert_eq!( format!("{:?}", node), "Text { value: \"a\", position: None }", "should support `Debug`" ); assert_eq!(node.to_string(), "a", "should support `ToString`"); assert_eq!(node.children_mut(), None, "should support `children_mut`"); assert_eq!(node.children(), None, "should support `children`"); assert_eq!(node.position(), None, "should support `position`"); assert_eq!(node.position_mut(), None, "should support `position`"); node.position_set(Some(Position::new(1, 1, 0, 1, 2, 1))); assert_eq!( format!("{:?}", node), "Text { value: \"a\", position: Some(1:1-1:2 (0-1)) }", "should support `position_set`" ); } #[test] fn comment() { let mut node = Node::Comment(Comment { value: "a".into(), position: None, }); assert_eq!( format!("{:?}", node), "Comment { value: \"a\", position: None }", "should support `Debug`" ); assert_eq!(node.to_string(), "a", "should support `ToString`"); assert_eq!(node.children_mut(), None, "should support `children_mut`"); assert_eq!(node.children(), None, "should support `children`"); assert_eq!(node.position(), None, "should support `position`"); assert_eq!(node.position_mut(), None, "should support `position`"); node.position_set(Some(Position::new(1, 1, 0, 1, 2, 1))); assert_eq!( format!("{:?}", node), "Comment { value: \"a\", position: Some(1:1-1:2 (0-1)) }", "should support `position_set`" ); } #[test] fn mdx_expression() { let mut node = Node::MdxExpression(MdxExpression { value: "a".into(), stops: vec![], position: None, }); assert_eq!( format!("{:?}", node), "MdxExpression { value: \"a\", position: None, stops: [] }", "should support `Debug`" ); assert_eq!(node.to_string(), "a", "should support `ToString`"); assert_eq!(node.children_mut(), None, "should support `children_mut`"); assert_eq!(node.children(), None, "should support `children`"); assert_eq!(node.position(), None, "should support `position`"); assert_eq!(node.position_mut(), None, "should support `position`"); node.position_set(Some(Position::new(1, 1, 0, 1, 2, 1))); assert_eq!( format!("{:?}", node), "MdxExpression { value: \"a\", position: Some(1:1-1:2 (0-1)), stops: [] }", "should support `position_set`" ); } #[test] fn mdxjs_esm() { let mut node = Node::MdxjsEsm(MdxjsEsm { value: "a".into(), stops: vec![], position: None, }); assert_eq!( format!("{:?}", node), "MdxjsEsm { value: \"a\", position: None, stops: [] }", "should support `Debug`" ); assert_eq!(node.to_string(), "a", "should support `ToString`"); assert_eq!(node.children_mut(), None, "should support `children_mut`"); assert_eq!(node.children(), None, "should support `children`"); assert_eq!(node.position(), None, "should support `position`"); assert_eq!(node.position_mut(), None, "should support `position`"); node.position_set(Some(Position::new(1, 1, 0, 1, 2, 1))); assert_eq!( format!("{:?}", node), "MdxjsEsm { value: \"a\", position: Some(1:1-1:2 (0-1)), stops: [] }", "should support `position_set`" ); } // Voids. #[test] fn doctype() { let mut node = Node::Doctype(Doctype { position: None }); assert_eq!( format!("{:?}", node), "Doctype { position: None }", "should support `Debug`" ); assert_eq!(node.to_string(), "", "should support `ToString`"); assert_eq!(node.children_mut(), None, "should support `children_mut`"); assert_eq!(node.children(), None, "should support `children`"); assert_eq!(node.position(), None, "should support `position`"); assert_eq!(node.position_mut(), None, "should support `position`"); node.position_set(Some(Position::new(1, 1, 0, 1, 2, 1))); assert_eq!( format!("{:?}", node), "Doctype { position: Some(1:1-1:2 (0-1)) }", "should support `position_set`" ); } // Parents. #[test] fn root() { let mut node = Node::Root(Root { position: None, children: vec![], }); assert_eq!( format!("{:?}", node), "Root { children: [], position: None }", "should support `Debug`" ); assert_eq!(node.to_string(), "", "should support `ToString`"); assert_eq!( node.children_mut(), Some(&mut vec![]), "should support `children_mut`" ); assert_eq!(node.children(), Some(&vec![]), "should support `children`"); assert_eq!(node.position(), None, "should support `position`"); assert_eq!(node.position_mut(), None, "should support `position`"); node.position_set(Some(Position::new(1, 1, 0, 1, 2, 1))); assert_eq!( format!("{:?}", node), "Root { children: [], position: Some(1:1-1:2 (0-1)) }", "should support `position_set`" ); } #[test] fn element() { let mut node = Node::Element(Element { tag_name: "a".into(), properties: vec![], position: None, children: vec![], }); assert_eq!( format!("{:?}", node), "Element { tag_name: \"a\", properties: [], children: [], position: None }", "should support `Debug`" ); assert_eq!(node.to_string(), "", "should support `ToString`"); assert_eq!( node.children_mut(), Some(&mut vec![]), "should support `children_mut`" ); assert_eq!(node.children(), Some(&vec![]), "should support `children`"); assert_eq!(node.position(), None, "should support `position`"); assert_eq!(node.position_mut(), None, "should support `position`"); node.position_set(Some(Position::new(1, 1, 0, 1, 2, 1))); assert_eq!( format!("{:?}", node), "Element { tag_name: \"a\", properties: [], children: [], position: Some(1:1-1:2 (0-1)) }", "should support `position_set`" ); } #[test] fn mdx_jsx_element() { let mut node = Node::MdxJsxElement(MdxJsxElement { name: None, attributes: vec![], position: None, children: vec![], }); assert_eq!( format!("{:?}", node), "MdxJsxElement { name: None, attributes: [], children: [], position: None }", "should support `Debug`" ); assert_eq!(node.to_string(), "", "should support `ToString`"); assert_eq!( node.children_mut(), Some(&mut vec![]), "should support `children_mut`" ); assert_eq!(node.children(), Some(&vec![]), "should support `children`"); assert_eq!(node.position(), None, "should support `position`"); assert_eq!(node.position_mut(), None, "should support `position`"); node.position_set(Some(Position::new(1, 1, 0, 1, 2, 1))); assert_eq!( format!("{:?}", node), "MdxJsxElement { name: None, attributes: [], children: [], position: Some(1:1-1:2 (0-1)) }", "should support `position_set`" ); } }