use std::{cell::RefCell, rc::Rc};
use crate::tree::{
Document, NodeType, XMLTreeError,
convert::NodeKind,
node::{InternalNodeSpec, Node, NodeCore, NodeSpec},
};
#[derive(Debug, Clone, Copy)]
enum State {
None,
HasAnElement,
HasDocumentType,
AsDocument,
AsContent { elem: usize, text: usize },
AsDocumentType { decl: usize },
}
pub struct DocumentFragmentSpec {
first_child: Option<Rc<RefCell<NodeCore<dyn NodeSpec>>>>,
last_child: Option<Rc<RefCell<NodeCore<dyn NodeSpec>>>>,
state: State,
}
impl NodeSpec for DocumentFragmentSpec {
fn node_type(&self) -> NodeType {
NodeType::DocumentFragment
}
fn first_child(&self) -> Option<Rc<RefCell<NodeCore<dyn NodeSpec>>>> {
self.first_child.clone()
}
fn last_child(&self) -> Option<Rc<RefCell<NodeCore<dyn NodeSpec>>>> {
self.last_child.clone()
}
}
impl InternalNodeSpec for DocumentFragmentSpec {
fn set_first_child(&mut self, new: Rc<RefCell<NodeCore<dyn NodeSpec>>>) {
self.first_child = Some(new);
}
fn unset_first_child(&mut self) {
self.first_child = None;
}
fn set_last_child(&mut self, new: Rc<RefCell<NodeCore<dyn NodeSpec>>>) {
self.last_child = Some(new);
}
fn unset_last_child(&mut self) {
self.last_child = None;
}
fn pre_child_removal(&mut self, removed_child: Node<dyn NodeSpec>) -> Result<(), XMLTreeError> {
match removed_child.node_type() {
NodeType::AttlistDecl
| NodeType::ElementDecl
| NodeType::EntityDecl
| NodeType::NotationDecl => match &mut self.state {
State::AsDocumentType { decl } => {
*decl -= 1;
if *decl == 0 {
self.state = State::None;
}
}
_ => unreachable!(),
},
NodeType::DocumentType => match self.state {
State::AsDocument => self.state = State::HasAnElement,
State::HasDocumentType => self.state = State::None,
_ => unreachable!(),
},
NodeType::Element => match &mut self.state {
State::AsContent { elem, text } => {
*elem -= 1;
if *elem == 0 && *text == 0 {
self.state = State::None;
}
}
State::AsDocument => self.state = State::HasDocumentType,
State::HasAnElement => self.state = State::None,
_ => unreachable!(),
},
NodeType::CDATASection | NodeType::Text => match &mut self.state {
State::AsContent { elem, text } => {
*text -= 1;
if *elem == 0 && *text == 0 {
self.state = State::None;
}
}
_ => unreachable!(),
},
NodeType::EntityReference => match &mut self.state {
State::AsContent { elem, text } => {
*text -= 1;
if *elem == 0 && *text == 0 {
self.state = State::None;
}
}
State::AsDocumentType { decl } => {
*decl -= 1;
if *decl == 0 {
self.state = State::None;
}
}
_ => unreachable!(),
},
NodeType::Comment | NodeType::ProcessingInstruction => {}
_ => {}
}
Ok(())
}
fn pre_child_insertion(
&self,
inserted_child: Node<dyn NodeSpec>,
mut preceding_node: Option<Node<dyn NodeSpec>>,
) -> Result<(), super::XMLTreeError> {
match inserted_child.downcast() {
NodeKind::AttlistDecl(_)
| NodeKind::ElementDecl(_)
| NodeKind::EntityDecl(_)
| NodeKind::NotationDecl(_) => {
if !matches!(self.state, State::None | State::AsDocumentType { .. }) {
return Err(XMLTreeError::UnacceptableHierarchy);
}
}
NodeKind::DocumentType(_) => match self.state {
State::AsContent { .. }
| State::AsDocument
| State::AsDocumentType { .. }
| State::HasDocumentType => {
return Err(XMLTreeError::UnacceptableHierarchy);
}
State::HasAnElement => {
while let Some(prev) = preceding_node {
preceding_node = prev.previous_sibling();
if matches!(prev.node_type(), NodeType::Element) {
return Err(XMLTreeError::UnacceptableHorizontality);
}
}
}
State::None => {}
},
NodeKind::Element(_) => match self.state {
State::HasDocumentType => {
while let Some(prev) = preceding_node.as_ref() {
if matches!(prev.node_type(), NodeType::DocumentType) {
break;
}
preceding_node = prev.previous_sibling();
}
if preceding_node.is_none() {
return Err(XMLTreeError::UnacceptableHorizontality);
}
}
State::AsDocument | State::AsDocumentType { .. } => {
return Err(XMLTreeError::UnacceptableHierarchy);
}
State::AsContent { .. } | State::HasAnElement | State::None => {}
},
NodeKind::CDATASection(_) | NodeKind::Text(_) => {
if !matches!(
self.state,
State::AsContent { .. } | State::HasAnElement | State::None
) {
return Err(XMLTreeError::UnacceptableHierarchy);
}
}
NodeKind::EntityReference(entity) => {
#[allow(clippy::if_same_then_else)]
if entity.name().starts_with('%') {
return Err(XMLTreeError::Unsupported);
} else if !matches!(
self.state,
State::AsContent { .. } | State::HasAnElement | State::None
) {
return Err(XMLTreeError::UnacceptableHierarchy);
}
}
NodeKind::Comment(_) | NodeKind::ProcessingInstruction(_) => {}
_ => return Err(XMLTreeError::UnacceptableHierarchy),
}
Ok(())
}
fn post_child_insertion(&mut self, inserted_child: Node<dyn NodeSpec>) {
match inserted_child.downcast() {
NodeKind::AttlistDecl(_)
| NodeKind::ElementDecl(_)
| NodeKind::EntityDecl(_)
| NodeKind::NotationDecl(_) => match &mut self.state {
State::AsDocumentType { decl } => *decl += 1,
_ => self.state = State::AsDocumentType { decl: 1 },
},
NodeKind::DocumentType(_) => {
if matches!(self.state, State::None) {
self.state = State::HasDocumentType;
} else {
self.state = State::AsDocument;
}
}
NodeKind::Element(_) => match &mut self.state {
State::HasAnElement => self.state = State::AsContent { elem: 2, text: 0 },
State::AsContent { elem, .. } => *elem += 1,
State::None => self.state = State::HasAnElement,
State::HasDocumentType => self.state = State::AsDocument,
State::AsDocument | State::AsDocumentType { .. } => unreachable!(),
},
NodeKind::CDATASection(_) | NodeKind::Text(_) => match &mut self.state {
State::AsContent { text, .. } => *text += 1,
State::HasAnElement => self.state = State::AsContent { elem: 1, text: 1 },
State::None => self.state = State::AsContent { elem: 0, text: 1 },
_ => unreachable!(),
},
NodeKind::EntityReference(entity) => {
if entity.name().starts_with('%') {
match &mut self.state {
State::AsDocumentType { decl } => *decl += 1,
State::None => self.state = State::AsDocumentType { decl: 1 },
_ => unreachable!(),
}
} else {
match &mut self.state {
State::AsContent { text, .. } => *text += 1,
State::HasAnElement => self.state = State::AsContent { elem: 1, text: 1 },
State::None => self.state = State::AsContent { elem: 0, text: 1 },
_ => unreachable!(),
}
}
}
NodeKind::Comment(_) | NodeKind::ProcessingInstruction(_) => {}
_ => {}
}
}
}
pub type DocumentFragment = Node<DocumentFragmentSpec>;
impl DocumentFragment {
pub(crate) fn new(owner_document: Document) -> Self {
Self {
core: Rc::new(RefCell::new(NodeCore {
parent_node: owner_document.core.borrow().parent_node.clone(),
previous_sibling: owner_document.core.borrow().previous_sibling.clone(),
next_sibling: None,
spec: DocumentFragmentSpec {
first_child: None,
last_child: None,
state: State::None,
},
})),
owner_document: owner_document.core.clone(),
}
}
pub fn deep_copy(&self) -> Self {
Node::create_node(
DocumentFragmentSpec {
first_child: None,
last_child: None,
state: self.core.borrow().spec.state,
},
self.owner_document(),
)
}
pub fn deep_copy_subtree(&self) -> Result<Self, XMLTreeError> {
let mut ret = self.deep_copy();
let mut children = self.first_child();
while let Some(child) = children {
children = child.next_sibling();
ret.append_child(child.deep_copy_subtree()?)?;
}
Ok(ret)
}
}
impl std::fmt::Display for DocumentFragment {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut children = self.first_child();
while let Some(child) = children {
children = child.next_sibling();
write!(f, "{}", child)?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn document_fragment_doctype_insertion_test() {
let document = Document::new();
let mut doctype = document.create_document_type("root", None, None);
let mut elem1 = document.create_element("elem1", None).unwrap();
let elem2 = document.create_element("elem2", None).unwrap();
let mut frag = document.create_document_fragment();
frag.append_child(doctype.clone()).unwrap();
frag.append_child(elem1.clone()).unwrap();
assert!(frag.append_child(elem2.clone()).is_err());
assert!(
frag.first_child()
.and_then(|ch| ch.as_document_type())
.is_some()
);
assert!(
frag.last_child()
.and_then(|ch| ch.as_element())
.is_some_and(|elem| elem.name().as_ref() == "elem1")
);
elem1.detach().unwrap();
assert!(
frag.first_child()
.and_then(|ch| ch.as_document_type())
.is_some()
);
assert!(
frag.last_child()
.and_then(|ch| ch.as_document_type())
.is_some()
);
assert!(doctype.insert_previous_sibling(elem1.clone()).is_err());
assert!(
frag.first_child()
.and_then(|ch| ch.as_document_type())
.is_some()
);
assert!(
frag.last_child()
.and_then(|ch| ch.as_document_type())
.is_some()
);
doctype.detach().unwrap();
assert!(frag.first_child().is_none());
assert!(frag.last_child().is_none());
frag.append_child(elem1.clone()).unwrap();
assert!(
frag.first_child()
.and_then(|ch| ch.as_element())
.is_some_and(|elem| elem.name().as_ref() == "elem1")
);
assert!(
frag.last_child()
.and_then(|ch| ch.as_element())
.is_some_and(|elem| elem.name().as_ref() == "elem1")
);
elem1.insert_previous_sibling(doctype.clone()).unwrap();
doctype.detach().unwrap();
assert!(frag.append_child(doctype.clone()).is_err());
frag.append_child(elem2).unwrap();
assert!(
frag.first_child()
.and_then(|ch| ch.as_element())
.is_some_and(|elem| elem.name().as_ref() == "elem1")
);
assert!(
frag.last_child()
.and_then(|ch| ch.as_element())
.is_some_and(|elem| elem.name().as_ref() == "elem2")
);
assert!(elem1.insert_previous_sibling(doctype).is_err());
}
#[test]
fn document_fragment_insertion_to_other_test() {
let document = Document::new();
let mut elem = document.create_element("root", None).unwrap();
let mut frag = document.create_document_fragment();
frag.append_child(document.create_element("child1", None).unwrap())
.unwrap();
frag.append_child(document.create_text("text1")).unwrap();
frag.append_child(document.create_element("child2", None).unwrap())
.unwrap();
elem.append_child(frag.clone()).unwrap();
let mut children = elem.first_child();
for expect in ["child1", "text1", "child2"] {
match children.as_ref().unwrap().downcast() {
NodeKind::Element(elem) => {
assert_eq!(elem.name().as_ref(), expect);
}
NodeKind::Text(text) => {
assert_eq!(&*text.data(), expect);
}
_ => unreachable!(),
}
children = children.unwrap().next_sibling();
}
assert!(children.is_none());
assert!(frag.first_child().is_none());
assert!(frag.last_child().is_none());
}
#[test]
fn document_fragment_wrong_insertion_to_other_test() {
let mut document = Document::new();
let mut frag = document.create_document_fragment();
frag.append_child(document.create_comment("comment1"))
.unwrap();
frag.append_child(document.create_comment("comment2"))
.unwrap();
frag.append_child(document.create_text("text1")).unwrap();
frag.append_child(document.create_text("text2")).unwrap();
frag.append_child(document.create_comment("comment3"))
.unwrap();
assert!(document.append_child(frag.clone()).is_err());
assert!(document.first_child().is_none());
assert!(document.last_child().is_none());
let mut children = frag.first_child();
for expect in ["comment1", "comment2", "text1", "text2", "comment3"] {
match children.as_ref().expect(expect).downcast() {
NodeKind::Comment(comment) => {
assert_eq!(&*comment.data(), expect);
}
NodeKind::Text(text) => {
assert_eq!(&*text.data(), expect);
}
_ => unreachable!(),
}
children = children.unwrap().next_sibling();
}
assert!(children.is_none());
}
}