parse-html 0.4.1

A simple Rust project to parse HTML.
Documentation
use crate::{
    lexer::lexer_trait::LexerTrait,
    node::{ElementNode, Node},
    parser::{error::ParserError, parser_trait::ParserTrait},
};

use super::{
    search_by_attr::{search_all_element_by_attr, search_element_by_attr},
    search_by_tag_name::{search_all_element_by_tag_name, search_element_by_tag_name},
};

pub struct DomTree {
    pub nodes: Vec<Node>,
}

impl DomTree {
    pub fn new<L, P>(html: &str) -> Result<Self, ParserError>
    where
        L: LexerTrait,
        P: ParserTrait,
    {
        let mut lexer = L::new(html);
        let tokens = lexer.tokenize();

        let mut parser = P::new(tokens);
        let nodes = parser.parse()?;

        Ok(Self { nodes })
    }

    pub fn get_by_id(&self, id_value: &str) -> Option<&ElementNode> {
        for node in &self.nodes {
            if let Some(found) = search_element_by_attr(node, "id", id_value) {
                return Some(found);
            }
        }
        None
    }

    pub fn get_all_by_id(&self, id_value: &str) -> Vec<&ElementNode> {
        let mut list_found = vec![];
        for node in &self.nodes {
            list_found.extend(search_all_element_by_attr(node, "id", id_value));
        }
        list_found
    }

    pub fn get_by_class(&self, value: &str) -> Option<&ElementNode> {
        for node in &self.nodes {
            if let Some(found) = search_element_by_attr(node, "class", value) {
                return Some(found);
            }
        }
        None
    }

    pub fn get_all_by_class(&self, value: &str) -> Vec<&ElementNode> {
        let mut list_found = vec![];
        for node in &self.nodes {
            list_found.extend(search_all_element_by_attr(node, "class", value));
        }
        list_found
    }

    pub fn get_by_tag_name(&self, tag_name: &str) -> Option<&ElementNode> {
        for node in &self.nodes {
            if let Some(found) = search_element_by_tag_name(node, tag_name) {
                return Some(found);
            }
        }
        None
    }

    pub fn get_all_by_tag_name(&self, tag_name: &str) -> Vec<&ElementNode> {
        let mut list_found = vec![];
        for node in &self.nodes {
            list_found.extend(search_all_element_by_tag_name(node, tag_name));
        }
        list_found
    }

    pub fn get_by_attr(
        &self,
        attributes_name: &str,
        attributes_value: &str,
    ) -> Option<&ElementNode> {
        for node in &self.nodes {
            if let Some(found) = search_element_by_attr(node, attributes_name, attributes_value) {
                return Some(found);
            }
        }
        None
    }

    pub fn get_all_by_attr(
        &self,
        attributes_name: &str,
        attributes_value: &str,
    ) -> Vec<&ElementNode> {
        let mut list_found = vec![];
        for node in &self.nodes {
            list_found.extend(search_all_element_by_attr(
                node,
                attributes_name,
                attributes_value,
            ));
        }
        list_found
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_chainning() {
        let grandchild_node = ElementNode {
            tag_name: "div".to_string(),
            attributes: vec![("id".to_string(), "grandchild_node".to_string())],
            children: vec![],
        };

        let child_node = ElementNode {
            tag_name: "div".to_string(),
            attributes: vec![
                ("id".to_string(), "child".to_string()),
                ("class".to_string(), "space".to_string()),
            ],
            children: vec![Node::Element(grandchild_node)],
        };

        let button = ElementNode {
            tag_name: "button".to_string(),
            attributes: vec![("class".to_string(), "btn".to_string())],
            children: vec![Node::Text("envoyer".to_string())],
        };

        let parent_node = ElementNode {
            tag_name: "div".to_string(),
            attributes: vec![("class".to_string(), "mt-1".to_string())],
            children: vec![Node::Element(child_node), Node::Element(button)],
        };

        let container = ElementNode {
            tag_name: "div".to_string(),
            attributes: vec![("class".to_string(), "container".to_string())],
            children: vec![Node::Element(parent_node)],
        };

        let found_parent = container.get_by_class("mt-1");
        let parent_element = found_parent.unwrap();
        let found_child = parent_element.get_by_class("space");
        let child_element = found_child.unwrap();
        let found_grandchild = child_element.get_by_id("grandchild_node");
        let found_child_not_exist = child_element.get_by_id("adfaf");
        let found_button = container.get_by_tag_name("button");
        let result_all_div_from_container = container.get_all_by_tag_name("div");
        let result_all_button = container.get_all_by_tag_name("button");
        let result_all_span = container.get_all_by_tag_name("span");

        assert!(found_grandchild.is_some());
        assert!(found_child_not_exist.is_none());
        assert!(found_button.is_some());
        assert!(result_all_div_from_container.len() == 3);
        assert!(result_all_button.len() == 1);
        assert!(result_all_span.len() == 0);
    }
}