use core::fmt; use query::{parse_query, QueryComponent, QueryElement, QueryParseError}; use tag::Tag; mod query; mod tag; pub struct Html { pub nodes: Vec, } impl Html { pub fn parse>(raw: S) -> Self { let mut raw = raw.as_ref(); let mut nodes = vec![]; loop { let Consumed { node, remaining } = Self::parse_node(raw); nodes.push(node); match remaining { None => break Self { nodes }, Some(rem) => raw = rem, } } } pub fn query>(&self, query: S) -> Result, QueryParseError> { let query = parse_query(query.as_ref())?; let mut working: Vec<&Node> = self.nodes.iter().collect(); for component in &query { let component_working: Vec<&Node> = std::mem::take(&mut working); for node in component_working { match component { QueryComponent::DirectElement(direct) => { if Self::node_matches(node, direct) { working.push(node); } } QueryComponent::Element(element) => { // GENNY: // you were working on the query system. This one needs to recur, // checking the current component against the entire depth of // children of the node. I'm worried about lifetimes here. For // immutable we might be alright but what happens when we're // muteable and we want to return a parent and it's child because // they both match? // // Don't get discouraged; it's a hard problem. Perhaps we write an // iterator so we don't have to keep references around? Sticking // everything in an Rc or whatever structure I'd need feels icky. // // Perhaps it's the way I'm searching. I could recur through the // current node, from self.nodes, until match failure. It fits // the funciton signature, returning one Node, but perhaps doesn't // fit the purpose. // // But the purpose is to easily insert parsed text into HTML // without templating it, so maybe it is fit for purpose. That // only needs to find one element at a time perhaps. // // All goes sour maybe I can return IDs to nodes? Keep a counter // in HTML where each node gets an increasing ID. Pass the ID to // a funtion in HTML for modification. Or maybe have IDs, // NodeHandles let's call them, contain an Rc>. // // That is by far enough thinking for the night. It's time to // head to bed. <3 i love you todo!() } } } } todo!() } fn node_matches(node: &Node, component: &QueryElement) -> bool { if let Node::Tag(tag) = node { if let Some(comp_name) = component.tag.as_deref() { if comp_name != tag.name { return false; } } match (tag.id(), component.id.as_deref()) { (None, Some(_)) => return false, (Some(tag_id), Some(comp_id)) if tag_id != comp_id => return false, _ => (), } if let Some(comp_class) = component.class.as_deref() { if !tag.has_class(comp_class) { return false; } } true } else { false } } fn parse_node(raw: &str) -> Consumed { match Self::is_tag(raw) { Some(_) => { if let Some(cmt) = Self::parse_comment(raw) { cmt } else { Self::parse_tag(raw) } } None => { let cons = Self::parse_text(raw); cons } } } fn parse_tag(raw: &str) -> Consumed { let (root_tag, mut rest) = Self::is_tag(raw).unwrap(); let mut tag = if root_tag.body.is_empty() { Tag { name: root_tag.name.to_owned(), body: None, self_closing: root_tag.self_closing, children: vec![], } } else { Tag { name: root_tag.name.into(), body: Some(root_tag.body.to_owned()), self_closing: root_tag.self_closing, children: vec![], } }; if root_tag.closing { panic!( "found closing tag when not expected! {:?}\n{raw}", root_tag.name ) } else if root_tag.self_closing { return Consumed { node: Node::Tag(tag), remaining: rest, }; } println!("Looking for {}", root_tag.name); loop { // Special case "; let special = Html::special_parse(basic, "script"); assert_eq!(special.unwrap().0, "words words\n"); assert!(special.unwrap().1.is_empty()); } #[test] fn special_parse_correctly_ignore_non_start() { let nonstart = "first_line\nlet end = '';\n"; let special = Html::special_parse(nonstart, "script"); assert!(special.is_none()); } #[test] fn special_parse_correctly_handles_leading_whitespace() { let white = "words words\n \t\t"; let special = Html::special_parse(white, "script"); assert_eq!(special.unwrap().0, "words words\n \t\t"); } #[test] fn parse_node_parses_comment() { let cmt = ""; let node = Html::parse_node(cmt); assert_eq!(node.node, comment!(" Comment! ")); } #[test] fn parse_node_parses_tag() { let basic = "

Hello!

"; let hh = Html::parse_node(basic); assert_eq!(hh.node, tag!("p", [text!("Hello!")])) } #[test] fn parse_node_parses_nested_tags() { let nested = "

Hello!

"; let hh = Html::parse_node(nested); assert_eq!(hh.node, tag!("p", [tag!("p", [text!("Hello!")])])) } #[test] fn parse_multiple_toplevel() { let nested = "

Hello

World!

"; let hh = Html::parse(nested); assert_eq!( hh.nodes, vec![tag!("p", [text!("Hello ")]), tag!("p", [text!("World!")])] ) } #[test] fn parse_script() { let raw = "\n\t\n"; let hh = Html::parse(raw); assert_eq!( hh.nodes, vec![tag!( "head", [ text!("\n\t"), tag!("script", [text!("let k=\"v\";\n\t")]), text!("\n") ] )] ) } #[test] fn parse_external_script() { let raw = "\n\t\n"; let hh = Html::parse(raw); assert_eq!( hh.nodes, vec![tag!( "head", [ text!("\n\t"), tag!("script", "src=\"script.js\""), text!("\n") ] )] ) } fn test_roundtrip(raw: &str) { let html = Html::parse(raw); let string = html.to_string(); for (raw, html) in raw.lines().zip(string.lines()) { assert_eq!(raw, html) } } #[test] fn round_trip_simple() { test_roundtrip("

Hello!

") } #[test] fn round_trip_complex() { test_roundtrip( r#" Title!

Hello, World!

"#, ) } }