use mesdoc::interface::{
BoxDynElement, BoxDynNode, BoxDynText, BoxDynUncareNode, Elements, IDocumentTrait, IElementTrait,
IErrorHandle, INodeTrait, ITextTrait, IUncareNodeTrait, InsertPosition, MaybeDoc, MaybeElement,
Texts,
};
use mesdoc::{self, error::Error as IError, utils::retain_by_index};
use rphtml::{
config::RenderOptions,
entity::{encode, EncodeType::NamedOrDecimal, EntitySet::SpecialChars},
parser::{
allow_insert, is_content_tag, Attr, AttrData, Doc, DocHolder, NameCase, Node, NodeType, RefNode,
},
};
use std::error::Error;
use std::rc::Rc;
use std::{any::Any, cell::RefCell};
pub mod types {
pub use mesdoc::interface::{
BoxDynElement, BoxDynNode, BoxDynText, Elements, IAttrValue, IEnumTyped, INodeType, Texts,
};
}
pub mod html {
pub use rphtml::config::ParseOptions;
}
use crate::html::ParseOptions;
use crate::types::{IAttrValue, IEnumTyped, INodeType};
struct Dom {
node: Rc<RefCell<Node>>,
}
impl Dom {
fn halt(&self, method: &str, message: &str) {
if let Some(doc) = &self.owner_document() {
doc.trigger_error(Box::new(IError::InvalidTraitMethodCall {
method: String::from(method),
message: String::from(message),
}));
}
}
fn validate_dom_change(&self, node: &BoxDynElement, method: &str) -> bool {
let my_node_type = self.node.borrow().node_type;
if my_node_type != NodeType::Tag {
self.halt(
method,
&format!("Can't {} for a {:?} type", method, my_node_type),
);
return false;
}
if let INodeType::Document = node.node_type() {
self.halt(method, &format!("Can't {} of a document type", method));
return false;
}
if self.is(&node) {
self.halt(method, &format!("Can't {} of self.", method));
return false;
}
let mut cur = self.cloned();
while let Some(parent) = &cur.parent() {
if parent.is(&node) {
self.halt(method, &format!("Can't {} of self's parent", method));
return false;
}
cur = parent.cloned();
}
true
}
}
fn to_static_str(orig: String) -> &'static str {
Box::leak(orig.into_boxed_str())
}
fn reset_next_siblings_index(start_index: usize, childs: &[RefNode]) {
for (step, node) in childs.iter().enumerate() {
node.borrow_mut().index = start_index + step;
}
}
fn remove_not_allowed_nodes(tag_name: &str, nodes: &mut Vec<RefNode>) -> bool {
let mut not_allowed_indexs: Vec<usize> = Vec::with_capacity(nodes.len());
let orig_len = nodes.len();
for (index, node) in nodes.iter().enumerate() {
if !allow_insert(tag_name, node.borrow().node_type) {
not_allowed_indexs.push(index);
}
}
if !not_allowed_indexs.is_empty() {
retain_by_index(nodes, ¬_allowed_indexs);
}
let now_allowed_len = not_allowed_indexs.len();
now_allowed_len > 0 && now_allowed_len != orig_len
}
impl INodeTrait for Dom {
fn to_node(self: Box<Self>) -> Box<dyn Any> {
self
}
fn index(&self) -> usize {
self.node.borrow().index
}
fn clone_node<'b>(&self) -> BoxDynNode<'b> {
Box::new(Dom {
node: self.node.clone(),
})
}
fn typed<'b>(self: Box<Self>) -> IEnumTyped<'b> {
match self.node_type() {
INodeType::Element | INodeType::DocumentFragement | INodeType::Document => {
IEnumTyped::Element(self as BoxDynElement)
}
INodeType::Text => IEnumTyped::Text(self as BoxDynText),
_ => IEnumTyped::UncareNode(self as BoxDynUncareNode),
}
}
fn node_type(&self) -> INodeType {
let node_type = self.node.borrow().node_type;
match node_type {
NodeType::AbstractRoot => {
let (is_document, _) = self.node.borrow().is_document();
if is_document {
INodeType::Document
} else {
INodeType::DocumentFragement
}
}
NodeType::Comment => INodeType::Comment,
NodeType::Text | NodeType::SpacesBetweenTag => INodeType::Text,
NodeType::Tag => INodeType::Element,
NodeType::XMLCDATA => INodeType::XMLCDATA,
NodeType::HTMLDOCTYPE => INodeType::HTMLDOCTYPE,
_ => INodeType::Other,
}
}
fn parent<'b>(&self) -> MaybeElement<'b> {
if let Some(parent) = &self.node.borrow().parent {
if let Some(node) = parent.upgrade() {
let cur = Dom { node };
return Some(Box::new(cur));
}
}
None
}
fn uuid(&self) -> Option<&str> {
None
}
fn owner_document(&self) -> MaybeDoc {
if let Some(root) = &self.node.borrow().root {
if let Some(root) = &root.upgrade() {
if let Some(doc) = &root.borrow().document {
return Some(Box::new(Document {
doc: Rc::clone(doc).into(),
}));
}
}
}
None
}
fn text_content(&self) -> &str {
to_static_str(self.node.borrow().build(
&RenderOptions {
decode_entity: true,
..Default::default()
},
matches!(self.node_type(), INodeType::Element),
))
}
fn set_text(&mut self, content: &str) {
let node_type = self.node_type();
match node_type {
INodeType::Element => {
let tag_name = self.tag_name();
let no_content_tag = !is_content_tag(tag_name);
let mut node = self.node.borrow_mut();
if !content.is_empty() {
if no_content_tag {
let content = encode(content, SpecialChars, NamedOrDecimal);
let mut text_node = Node::create_text_node(&content, None);
text_node.parent = Some(Rc::downgrade(&self.node));
node.childs = Some(vec![Rc::new(RefCell::new(text_node))]);
} else {
node.content = Some(content.chars().collect::<Vec<char>>());
}
} else {
if no_content_tag {
node.childs = None;
} else {
node.content = None;
}
}
}
INodeType::Text => {
if content.is_empty() {
self.halt("set_text",
"the text parameter can't be empty, if you want to remove a text node, you can use 'remove' method instead."
);
} else {
self.node.borrow_mut().content = Some(content.chars().collect::<Vec<char>>());
}
}
_ => {
}
}
}
fn set_html(&mut self, content: &str) {
let mut is_element = true;
let target = match self.node_type() {
INodeType::Element => Some(Rc::clone(&self.node)),
INodeType::Text => {
if let Some(parent) = &self.node.borrow_mut().parent {
if let Some(parent) = &parent.upgrade() {
is_element = false;
Some(Rc::clone(parent))
} else {
None
}
} else {
None
}
}
_ => None,
};
if let Some(target) = &target {
let tag_name = target
.borrow()
.meta
.as_ref()
.map(|meta| meta.borrow().get_name(None))
.expect("A tag use `set_html` must have a tag name.");
if is_content_tag(&tag_name.to_ascii_lowercase()) {
target.borrow_mut().content = Some(content.chars().collect::<Vec<char>>());
} else {
let doc_holder = Doc::parse(
content,
ParseOptions {
auto_fix_unexpected_endtag: true,
auto_fix_unescaped_lt: true,
..Default::default()
},
)
.unwrap();
if let Some(nodes) = &mut doc_holder.get_root_node().borrow_mut().childs {
let mut nodes = nodes.split_off(0);
let has_not_allowed = remove_not_allowed_nodes(&tag_name, &mut nodes);
let has_nodes = !nodes.is_empty();
if has_nodes {
for node in &nodes {
node.borrow_mut().parent = Some(Rc::downgrade(target));
}
}
if is_element {
if has_not_allowed {
reset_next_siblings_index(0, &nodes);
}
(*target.borrow_mut()).childs = if has_nodes { Some(nodes) } else { None };
} else if let Some(childs) = &mut target.borrow_mut().childs {
let index = self.index();
if index < childs.len() - 1 {
reset_next_siblings_index(index + nodes.len(), &childs[index + 1..]);
}
if has_nodes {
reset_next_siblings_index(index, &nodes);
childs.splice(index..index + 1, nodes);
} else {
childs.remove(index);
}
} else {
}
} else {
target.borrow_mut().childs = None;
}
}
}
}
}
impl ITextTrait for Dom {
fn remove(self: Box<Self>) {
let index = self.index();
if let Some(parent) = &self.node.borrow_mut().parent {
if let Some(parent) = parent.upgrade() {
if let Some(childs) = &mut parent.borrow_mut().childs {
childs.remove(index);
reset_next_siblings_index(index, &childs[index..]);
}
}
}
}
fn append_text(&mut self, content: &str) {
let chars = content.chars().collect::<Vec<char>>();
if let Some(content) = &mut self.node.borrow_mut().content {
content.extend(chars);
} else {
self.node.borrow_mut().content = Some(chars);
}
}
fn prepend_text(&mut self, content: &str) {
let chars = content.chars().collect::<Vec<char>>();
if let Some(content) = &mut self.node.borrow_mut().content {
content.splice(0..0, chars);
} else {
self.node.borrow_mut().content = Some(chars);
}
}
}
impl IUncareNodeTrait for Dom {}
impl IElementTrait for Dom {
fn tag_name(&self) -> &str {
match self.node_type() {
INodeType::Element => {
if let Some(meta) = &self.node.borrow().meta {
let name = meta.borrow().get_name(Some(NameCase::Upper));
return to_static_str(name);
}
self.halt("tag_name", "Html syntax error: not found a tag name.");
}
INodeType::Document | INodeType::DocumentFragement => {}
cur_type => self.halt(
"tag_name",
&format!("The node type of '{:?}' doesn't have a tag name.", cur_type),
),
};
""
}
fn child_nodes_length(&self) -> usize {
self
.node
.borrow()
.childs
.as_ref()
.map_or(0, |childs| childs.len())
}
fn child_nodes_item<'b>(&self, index: usize) -> Option<BoxDynNode<'b>> {
if let Some(childs) = &self.node.borrow().childs {
return childs.get(index).map(|node| {
Box::new(Dom {
node: Rc::clone(node),
}) as BoxDynNode
});
}
None
}
fn get_attribute(&self, name: &str) -> Option<IAttrValue> {
if let Some(meta) = &self.node.borrow().meta {
let attrs = &meta.borrow().attrs;
if !attrs.is_empty() {
let name = name.to_ascii_lowercase();
for attr in attrs {
if let Some(key) = &attr.key {
if key.content.to_ascii_lowercase() == name {
if let Some(value) = &attr.value {
let attr_value = value.content.clone();
return Some(IAttrValue::Value(attr_value, attr.quote));
} else {
return Some(IAttrValue::True);
}
}
}
}
}
}
None
}
fn set_attribute(&mut self, name: &str, value: Option<&str>) {
let mut need_quote = false;
let mut quote: char = '"';
if let Some(meta) = &self.node.borrow().meta {
let value = value.map(|v| {
let mut find_quote: bool = false;
let mut content = String::with_capacity(v.len());
for ch in v.chars() {
if !need_quote {
need_quote = Attr::need_quoted_char(&ch);
}
if ch == '"' || ch == '\'' {
if find_quote {
if quote == ch {
content.push('\\');
}
} else {
find_quote = true;
if ch == '"' {
quote = '\'';
}
}
}
content.push(ch);
}
AttrData { content }
});
for attr in &mut meta.borrow_mut().attrs {
if let Some(key) = &attr.key {
if key.content == name {
attr.value = value;
return;
}
}
}
let quote = if value.is_some() { Some(quote) } else { None };
meta.borrow_mut().attrs.push(Attr {
key: Some(AttrData {
content: name.into(),
}),
value,
quote,
need_quote,
});
}
}
fn remove_attribute(&mut self, name: &str) {
if let Some(meta) = &self.node.borrow().meta {
let find_index = meta.borrow().attrs.iter().position(|attr| {
if let Some(key) = &attr.key {
return key.content == name;
}
false
});
if let Some(index) = find_index {
meta.borrow_mut().attrs.remove(index);
}
}
}
fn inner_html(&self) -> &str {
to_static_str(self.node.borrow().build(
&RenderOptions {
inner_html: true,
encode_content: true,
..Default::default()
},
false,
))
}
fn outer_html(&self) -> &str {
to_static_str(self.node.borrow().build(
&RenderOptions {
encode_content: true,
..Default::default()
},
false,
))
}
fn remove_child(&mut self, ele: BoxDynElement) {
if let Some(parent) = &ele.parent() {
if self.is(parent) {
if let Some(childs) = self.node.borrow_mut().childs.as_mut() {
let index = ele.index();
if index != childs.len() - 1 {
reset_next_siblings_index(index, &childs[index + 1..]);
}
childs.remove(index);
}
}
}
}
fn insert_adjacent(&mut self, position: &InsertPosition, node: &BoxDynElement) {
let action = position.action();
if !self.validate_dom_change(&node, action) {
return;
}
let node_type = node.node_type();
let specified: Box<dyn Any> = node.cloned().to_node();
if let Ok(dom) = specified.downcast::<Dom>() {
let mut nodes = match node_type {
INodeType::DocumentFragement => {
if let Some(childs) = &dom.node.borrow().childs {
childs
.iter()
.map(|v| Rc::clone(&v))
.collect::<Vec<RefNode>>()
} else {
vec![]
}
}
_ => {
if let Some(parent) = &mut node.parent() {
parent.remove_child(node.cloned());
}
vec![dom.node]
}
};
let tag_name = self.tag_name();
remove_not_allowed_nodes(tag_name, &mut nodes);
if nodes.is_empty() {
return;
}
use InsertPosition::*;
match position {
BeforeBegin | AfterEnd => {
let mut index = self.index();
let mut nexts: Vec<RefNode> = vec![];
let insert_len = nodes.len();
if *position == AfterEnd {
index += 1;
}
reset_next_siblings_index(index, &nodes);
if let Some(parent) = &self.node.borrow_mut().parent {
if let Some(parent) = &parent.upgrade() {
if let Some(childs) = &mut parent.borrow_mut().childs {
if index < childs.len() {
nexts = childs.split_off(index);
}
for node in &nodes {
node.borrow_mut().parent = Some(Rc::downgrade(parent));
}
childs.extend(nodes);
}
}
}
if !nexts.is_empty() {
reset_next_siblings_index(index + insert_len, &nexts);
if let Some(parent) = &self.node.borrow_mut().parent {
if let Some(parent) = parent.upgrade() {
if let Some(childs) = &mut parent.borrow_mut().childs {
childs.extend(nexts);
}
}
}
}
}
AfterBegin | BeforeEnd => {
for node in &nodes {
node.borrow_mut().parent = Some(Rc::downgrade(&self.node));
}
if let Some(childs) = &mut self.node.borrow_mut().childs {
if *position == BeforeEnd {
reset_next_siblings_index(childs.len(), &nodes);
childs.extend(nodes);
} else {
reset_next_siblings_index(0, &nodes);
reset_next_siblings_index(nodes.len(), &childs);
nodes.append(childs);
*childs = nodes;
}
} else {
reset_next_siblings_index(0, &nodes);
self.node.borrow_mut().childs = Some(nodes);
}
}
}
} else {
self.halt(
action,
&format!("Can't {} that not implemented 'Dom'", action),
);
}
}
fn texts<'b>(&self, limit_depth: u32) -> Option<Texts<'b>> {
let limit_depth = if limit_depth == 0 {
u32::MAX
} else {
limit_depth
};
let mut result: Texts = Texts::with_capacity(5);
fn loop_handle(node: BoxDynElement, result: &mut Texts, cur_depth: u32, limit_depth: u32) {
let child_nodes = node.child_nodes();
if !child_nodes.is_empty() {
let next_depth = cur_depth + 1;
let recursive = next_depth < limit_depth;
for node in &node.child_nodes() {
match node.node_type() {
INodeType::Text => {
let node = node.clone_node();
let text = node.typed().into_text().expect("TextNode must true");
result.get_mut_ref().push(text);
}
INodeType::Element => {
if recursive {
let node = node.clone_node();
let ele = node.typed().into_element().expect("ElementNode must true");
loop_handle(ele, result, next_depth, limit_depth);
}
}
_ => {}
}
}
} else if is_content_tag(node.tag_name()) {
result.get_mut_ref().push(
node
.into_text()
.expect("Content tag must be able to translate into text node"),
);
}
}
let node = Box::new(Dom {
node: Rc::clone(&self.node),
}) as BoxDynElement;
loop_handle(node, &mut result, 0, limit_depth);
if !result.is_empty() {
return Some(result);
}
None
}
fn into_text<'b>(self: Box<Self>) -> Result<BoxDynText<'b>, Box<dyn Error>> {
if is_content_tag(self.tag_name()) {
Ok(self as BoxDynText)
} else {
Err(Box::new(IError::InvalidTraitMethodCall {
method: "into_text".into(),
message: "Can't call 'into_text' with tags those are not content tags.".into(),
}))
}
}
fn is(&self, ele: &BoxDynElement) -> bool {
let specified: Box<dyn Any> = ele.cloned().to_node();
if let Ok(dom) = specified.downcast::<Dom>() {
return Node::is_same(&self.node, &dom.node);
}
false
}
}
struct Document {
doc: DocHolder,
}
impl Document {
fn bind_error(&mut self, handle: IErrorHandle) {
*self.doc.borrow().onerror.borrow_mut() = Some(Rc::new(handle));
}
fn list<'b>(&self) -> Elements<'b> {
let root: Dom = Rc::clone(&self.doc.borrow().root).into();
Elements::with_nodes(vec![Box::new(root)])
}
}
impl IDocumentTrait for Document {
fn get_element_by_id<'b>(&self, id: &str) -> Option<BoxDynElement<'b>> {
if let Some(node) = self.doc.get_element_by_id(id) {
return Some(Box::new(Dom {
node: Rc::clone(&node),
}));
}
None
}
fn source_code(&self) -> &'static str {
to_static_str(self.doc.render(&Default::default()))
}
fn get_root_node<'b>(&self) -> BoxDynNode<'b> {
Box::new(Dom {
node: Rc::clone(&self.doc.borrow().root),
})
}
fn onerror(&self) -> Option<Rc<IErrorHandle>> {
if let Some(error_handle) = &(*self.doc.borrow().onerror.borrow()) {
Some(Rc::clone(error_handle))
} else {
None
}
}
}
impl From<Rc<RefCell<Node>>> for Dom {
fn from(node: Rc<RefCell<Node>>) -> Self {
Dom {
node: Rc::clone(&node),
}
}
}
pub struct Vis;
impl Vis {
pub(crate) fn options() -> ParseOptions {
ParseOptions {
auto_fix_unclosed_tag: true,
auto_fix_unexpected_endtag: true,
auto_fix_unescaped_lt: true,
allow_self_closing: true,
..Default::default()
}
}
pub(crate) fn parse_doc_options(
html: &str,
options: ParseOptions,
) -> Result<Document, Box<dyn Error>> {
mesdoc::init();
let doc = Doc::parse(html, options)?;
Ok(Document { doc })
}
pub fn load_options(html: &str, options: ParseOptions) -> Result<Elements, Box<dyn Error>> {
let doc = Vis::parse_doc_options(html, options)?;
Ok(doc.list())
}
pub fn load_options_catch(html: &str, options: ParseOptions, handle: IErrorHandle) -> Elements {
let doc = Vis::parse_doc_options(html, options);
if let Ok(mut doc) = doc {
doc.bind_error(handle);
doc.list()
} else {
handle(doc.err().unwrap());
Elements::new()
}
}
pub fn load(html: &str) -> Result<Elements, Box<dyn Error>> {
Vis::load_options(html, Vis::options())
}
pub fn load_catch(html: &str, handle: IErrorHandle) -> Elements {
Vis::load_options_catch(html, Vis::options(), handle)
}
pub fn dom<'b>(ele: &BoxDynElement) -> Elements<'b> {
Elements::with_nodes(vec![ele.cloned()])
}
}