cfg_feat_text! {
use super::Texts;
}
use super::{BoxDynNode, BoxDynText, Elements, INodeTrait, INodeType};
use crate::mesdoc::error::{BoxDynError, Error as IError};
use std::ops::Range;
pub type BoxDynElement<'a> = Box<dyn IElementTrait + 'a>;
pub type MaybeElement<'a> = Option<BoxDynElement<'a>>;
#[derive(Debug)]
pub enum IAttrValue {
Value(String, Option<char>),
True,
}
impl IAttrValue {
pub fn is_true(&self) -> bool {
matches!(self, IAttrValue::True)
}
pub fn is_str(&self, value: &str) -> bool {
match self {
IAttrValue::Value(v, _) => v == value,
IAttrValue::True => value.is_empty(),
}
}
pub fn to_list(&self) -> Vec<&str> {
match self {
IAttrValue::Value(v, _) => v.trim().split_ascii_whitespace().collect::<Vec<&str>>(),
IAttrValue::True => vec![],
}
}
}
impl ToString for IAttrValue {
fn to_string(&self) -> String {
match self {
IAttrValue::Value(v, _) => v.clone(),
IAttrValue::True => String::new(),
}
}
}
#[derive(Debug)]
pub enum IFormValue {
Single(String),
Multiple(Vec<String>),
}
impl ToString for IFormValue {
fn to_string(&self) -> String {
match self {
IFormValue::Single(v) => v.clone(),
IFormValue::Multiple(v) => v.join(","),
}
}
}
impl IntoIterator for IFormValue {
type Item = String;
type IntoIter = std::vec::IntoIter<String>;
fn into_iter(self) -> Self::IntoIter {
match self {
IFormValue::Multiple(v) => v.into_iter(),
IFormValue::Single(_) => vec![].into_iter(),
}
}
}
cfg_feat_insertion! {
#[derive(Debug, PartialEq, Eq)]
pub enum InsertPosition {
BeforeBegin,
AfterBegin,
BeforeEnd,
AfterEnd,
}
impl InsertPosition {
pub fn action(&self) -> &'static str {
use InsertPosition::*;
match self {
BeforeBegin => "insert before",
AfterBegin => "prepend",
BeforeEnd => "append",
AfterEnd => "insert after",
}
}
}
}
pub trait IElementTrait: INodeTrait {
fn is(&self, ele: &BoxDynElement) -> bool;
fn is_root_element(&self) -> bool;
fn cloned<'b>(&self) -> BoxDynElement<'b> {
let ele = self.clone_node();
ele.typed().into_element().unwrap()
}
fn next_element_sibling<'b>(&self) -> MaybeElement<'b> {
if let Some(parent) = &self.parent() {
let index = self.index();
let total = parent.child_nodes_length();
for cur_index in index + 1..total {
let ele = parent
.child_nodes_item(cur_index)
.expect("Child nodes item index must less than total");
if matches!(ele.node_type(), INodeType::Element) {
return Some(
ele
.typed()
.into_element()
.expect("Call `typed` for element ele."),
);
}
}
}
None
}
fn next_element_siblings<'b>(&self) -> Elements<'b> {
if let Some(parent) = &self.parent() {
let index = self.index();
let total = parent.child_nodes_length();
let start_index = index + 1;
let mut result: Elements = Elements::with_capacity(total - start_index);
for cur_index in start_index..total {
let ele = parent
.child_nodes_item(cur_index)
.expect("Child nodes item index must less than total");
if matches!(ele.node_type(), INodeType::Element) {
result.push(
ele
.typed()
.into_element()
.expect("Call `typed` for element ele."),
);
}
}
return result;
}
Elements::new()
}
fn previous_element_sibling<'b>(&self) -> MaybeElement<'b> {
if let Some(parent) = &self.parent() {
let index = self.index();
if index > 0 {
for cur_index in (0..index).rev() {
let ele = parent
.child_nodes_item(cur_index)
.expect("Child nodes item index must less than total");
if matches!(ele.node_type(), INodeType::Element) {
return Some(
ele
.typed()
.into_element()
.expect("Call `typed` for element ele."),
);
}
}
}
}
None
}
fn previous_element_siblings<'b>(&self) -> Elements<'b> {
if let Some(parent) = &self.parent() {
let index = self.index();
if index > 0 {
let mut result: Elements = Elements::with_capacity(index);
for cur_index in 0..index {
let ele = parent
.child_nodes_item(cur_index)
.expect("Child nodes item index must less than total");
if matches!(ele.node_type(), INodeType::Element) {
result.push(
ele
.typed()
.into_element()
.expect("Call `typed` for element ele."),
);
}
}
return result;
}
}
Elements::new()
}
fn siblings<'b>(&self) -> Elements<'b> {
if let Some(parent) = &self.parent() {
let index = self.index();
if index == 0 {
return self.next_element_siblings();
}
let total = parent.child_nodes_length();
if index == total - 1 {
return self.previous_element_siblings();
}
let mut result: Elements = Elements::with_capacity(total - 1);
fn loop_handle(range: &Range<usize>, parent: &BoxDynElement, result: &mut Elements) {
for cur_index in range.start..range.end {
let ele = parent
.child_nodes_item(cur_index)
.expect("Child nodes item index must less than total");
if matches!(ele.node_type(), INodeType::Element) {
result.push(
ele
.typed()
.into_element()
.expect("Call `typed` for element ele."),
);
}
}
}
loop_handle(&(0..index), parent, &mut result);
loop_handle(&(index + 1..total), parent, &mut result);
return result;
}
Elements::new()
}
fn value(&self) -> IFormValue;
fn tag_name(&self) -> String {
self
.tag_names()
.iter()
.map(|ch| ch.to_ascii_uppercase())
.collect::<String>()
}
fn tag_names(&self) -> Vec<char>;
fn child_nodes_length(&self) -> usize;
fn child_nodes_item<'b>(&self, index: usize) -> Option<BoxDynNode<'b>>;
fn child_nodes_item_since_by<'a>(
&'a self,
node_index: usize,
reverse: bool,
handle: Box<dyn FnMut(&dyn IElementTrait) -> bool + 'a>,
);
fn child_nodes<'b>(&self) -> Vec<BoxDynNode<'b>> {
let total = self.child_nodes_length();
let mut result = Vec::with_capacity(total);
for index in 0..total {
result.push(
self
.child_nodes_item(index)
.expect("child nodes index must less than total."),
);
}
result
}
fn children<'b>(&self) -> Elements<'b> {
let total = self.child_nodes_length();
let mut result = Elements::with_capacity(total);
for index in 0..total {
let node = self
.child_nodes_item(index)
.expect("child nodes index must less than total.");
if let INodeType::Element = node.node_type() {
result.push(node.typed().into_element().unwrap());
}
}
result
}
fn children_by<'a>(&'a self, matcher: Box<dyn FnMut(&dyn IElementTrait) + 'a>);
fn get_attribute(&self, name: &str) -> Option<IAttrValue>;
fn set_attribute(&mut self, name: &str, value: Option<&str>);
fn remove_attribute(&mut self, name: &str);
fn has_attribute(&self, name: &str) -> bool {
self.get_attribute(name).is_some()
}
fn html(&self) -> String {
self.inner_html()
}
fn inner_html(&self) -> String;
fn outer_html(&self) -> String;
cfg_feat_insertion! {
fn insert_adjacent(&mut self, position: &InsertPosition, node: &BoxDynElement);
fn replace_with(&mut self, node: &BoxDynElement);
}
cfg_feat_mutation! {
fn remove_child(&mut self, ele: BoxDynElement);
}
cfg_feat_text! {
fn texts<'b>(&self, limit_depth: usize) -> Option<Texts<'b>> {
let handle = Box::new(|_: usize, _: &BoxDynText| true);
self.texts_by(limit_depth, &handle)
}
fn texts_by<'b>(
&self,
limit_depth: usize,
handle: &dyn Fn(usize, &BoxDynText) -> bool,
) -> Option<Texts<'b>> {
self.texts_by_rec(limit_depth, handle, &Box::new(|_: &BoxDynElement|true))
}
fn texts_by_rec<'b>(
&self,
_limit_depth: usize,
_handle: &dyn Fn(usize, &BoxDynText) -> bool,
_rec_handle: &dyn Fn(&BoxDynElement) -> bool
) -> Option<Texts<'b>>{
None
}
}
#[allow(clippy::boxed_local)]
fn into_text<'b>(self: Box<Self>) -> Result<BoxDynText<'b>, BoxDynError> {
Err(Box::new(IError::InvalidTraitMethodCall {
method: "into_text".into(),
message: "The into_text method is not implemented.".into(),
}))
}
}
#[cfg(test)]
mod tests {
use super::IAttrValue;
#[test]
fn test_i_attr_value() {
let attr_value = IAttrValue::Value("Hello".into(), None);
assert!(format!("{:?}", attr_value).contains("Hello"));
assert!(attr_value.is_str("Hello"));
assert!(!attr_value.is_true());
assert_eq!(attr_value.to_list(), vec!["Hello"]);
assert_eq!(attr_value.to_string(), "Hello");
let attr_value = IAttrValue::True;
assert!(attr_value.is_str(""));
assert_eq!(attr_value.to_string(), "");
assert!(attr_value.is_true());
assert!(attr_value.to_list().is_empty());
}
}