use std::any::Any;
use std::cell::OnceCell;
use std::cmp::Ordering;
use std::collections::HashMap;
use std::fmt::Formatter;
use std::hash::{Hash, Hasher};
use std::path::Path;
use regex::Regex;
use crate::errors::*;
pub struct Document {
declaration: Option<Declaration>,
dtds: Vec<DTD>,
root_element: Element
}
impl Document {
pub fn new(root: Element) -> Self {
Document::new_with_decl_dtd(root, Some(Declaration::default()), None)
}
pub fn new_with_decl_dtd(root: Element, declaration: Option<Declaration>, dtd: Option<&[DTD]>) -> Self {
Self{
declaration: declaration,
dtds: match dtd{
None => Vec::with_capacity(1),
Some(dtds) => Vec::from(dtds)
},
root_element: root
}
}
pub fn doctype_defs(&self) -> impl Iterator<Item = &DTD> {
self.dtds.iter()
}
pub fn doctype_defs_mut(&mut self) -> impl Iterator<Item = &mut DTD> {
self.dtds.iter_mut()
}
pub fn set_doctype_defs(&mut self, dtds: Option<&[DTD]>) {
match dtds {
None => self.dtds = Vec::with_capacity(1),
Some(dlist) => self.dtds = Vec::from(dlist)
}
}
pub fn declaration(&self) -> &Option<Declaration> {
&self.declaration
}
pub fn set_declaration(&mut self, decl: Declaration) {
self.declaration = Some(decl)
}
pub fn to_string(&self) -> String {
self.to_string_with_indent(" ")
}
pub fn to_string_with_indent(&self, indent: impl Into<String>) -> String {
let mut indent = indent.into();
match crate::validate_indent(indent.as_str()){
Ok(_) => {},
Err(_) => {
eprintln!("WARNING: {:?} is not a valid indentation. Must be either 1 tab or any number of spaces. The default of 2 spaces will be used instead", indent);
indent = " ".to_string();
}
};
let mut builder = String::new();
match &self.declaration{
None => {},
Some(decl) => {
builder.push_str(decl.to_string().as_str());
builder.push_str("\n");
}
}
for dtd in &self.dtds {
builder.push_str(dtd.to_string().as_str());
builder.push_str("\n");
}
builder.push_str(&self.root_element.to_string_with_indent(indent.as_str()));
builder.push_str("\n");
return builder;
}
pub fn write_to_filepath(&self, path: impl AsRef<Path>) -> std::io::Result<()> {
self.write_to_filepath_with_indent(path, " ")
}
pub fn write_to_filepath_with_indent(&self, path: impl AsRef<Path>, indent: impl Into<String>) -> std::io::Result<()> {
use std::fs;
match path.as_ref().parent(){
None => {}
Some(dir) => fs::create_dir_all(dir)?
};
fs::write(path, self.to_string_with_indent(indent))
}
pub fn write_to_file(&self, out: &mut impl std::io::Write) -> std::io::Result<()> {
self.write_to_file_with_indent(out, " ")
}
pub fn write_to_file_with_indent(&self, out: &mut impl std::io::Write, indent: impl Into<String>) -> std::io::Result<()> {
write!(out, "{}", self.to_string_with_indent(indent))
}
pub fn root_element(&self) -> &Element {
&self.root_element
}
pub fn root_element_mut(&mut self) -> &mut Element {
&mut self.root_element
}
}
impl std::fmt::Display for Document{
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.to_string_with_indent(" "))
}
}
impl std::fmt::Debug for Document{
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.to_string_with_indent(" "))
}
}
impl PartialEq<Self> for Document {
fn eq(&self, other: &Self) -> bool {
self.declaration == other.declaration
&& self.dtds == other.dtds
&& self.root_element == other.root_element
}
}
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub enum DomNodeType {
CDataNode,
CommentNode,
ElementNode,
TextNode
}
impl From<Box<dyn Node>> for DomNodeType {
fn from(value: Box<dyn Node>) -> Self {
value.node_type()
}
}
impl std::fmt::Display for DomNodeType {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
DomNodeType::CDataNode => write!(f, "CDATA"),
DomNodeType::CommentNode => write!(f, "Comment"),
DomNodeType::ElementNode => write!(f, "Element"),
DomNodeType::TextNode => write!(f, "Text"),
}
}
}
pub trait Node: dyn_clone::DynClone + std::fmt::Debug + std::fmt::Display + ToString {
fn text(&self) -> String;
fn is_element(&self) -> bool;
fn is_text(&self) -> bool;
fn is_comment(&self) -> bool;
fn is_cdata(&self) -> bool;
fn node_type(&self) -> DomNodeType {
if self.is_cdata() {
DomNodeType::CDataNode
} else if self.is_comment() {
DomNodeType::CommentNode
} else if self.is_element() {
DomNodeType::ElementNode
} else if self.is_text() {
DomNodeType::TextNode
} else {
panic!("Logic error! Box<dyn Node> value has no corresponding type in enum DomNodeType")
}
}
fn as_element(&self) -> Result<&Element, TypeCastError>;
fn as_comment(&self) -> Result<&Comment, TypeCastError>;
fn as_text(&self) -> Result<&Text, TypeCastError>;
fn as_cdata(&self) -> Result<&CData, TypeCastError>;
fn as_element_mut(&mut self) -> Result<&mut Element, TypeCastError>;
fn as_comment_mut(&mut self) -> Result<&mut Comment, TypeCastError>;
fn as_text_mut(&mut self) -> Result<&mut Text, TypeCastError>;
fn as_cdata_mut(&mut self) -> Result<&mut CData, TypeCastError>;
fn as_node(&self) -> &dyn Node;
fn as_node_mut(&mut self) -> &mut dyn Node;
fn as_any(&self) -> &dyn Any;
fn as_any_mut(&mut self) -> &mut dyn Any;
fn to_string_with_indent(&self, indent: &str) -> String;
fn boxed(self) -> Box<dyn Node>;
}
pub fn clone_node(node: &Box<dyn Node>) -> Box<dyn Node> {
if node.is_element() {
Box::new(node.as_element().expect("logic error").clone())
} else if node.is_text() {
Box::new(node.as_text().expect("logic error").clone())
} else if node.is_comment() {
Box::new(node.as_comment().expect("logic error").clone())
} else if node.is_cdata() {
Box::new(node.as_cdata().expect("logic error").clone())
} else {
panic!("logic error: Node is neither of Element, Text, Comment, nor CData");
}
}
pub fn node_eq(n1: &Box<dyn Node>, n2: &Box<dyn Node>) -> bool {
let t1 = n1.node_type();
let t2 = n2.node_type();
if t1 != t2 {
return false;
}
return match t1 {
DomNodeType::CDataNode =>
n1.as_cdata().unwrap() == n2.as_cdata().unwrap(),
DomNodeType::CommentNode =>
n1.as_comment().unwrap() == n2.as_comment().unwrap(),
DomNodeType::ElementNode =>
n1.as_element().unwrap() == n2.as_element().unwrap(),
DomNodeType::TextNode =>
n1.as_text().unwrap() == n2.as_text().unwrap()
}
}
pub struct Element {
name: String,
child_nodes: Vec<Box<dyn Node>>,
attributes: HashMap<String, String>,
xmlns: Option<String>,
xmlns_prefix: Option<String>,
xmlns_context: HashMap<String, String>
}
impl Element {
pub fn new<TEXT1: Into<String>+Clone, TEXT2: Into<String>+Clone>(
name: impl Into<String>, text: Option<String>,
attributes: Option<HashMap<TEXT1, TEXT2>>,
xmlns: Option<String>,
xmlns_prefix: Option<String>,
children: Option<Vec<Box<dyn Node>>>
) -> Result<Self, KissXmlError> {
let name = name.into();
Element::check_elem_name(name.as_str())?;
let mut attrs: HashMap<String, String> = HashMap::new();
match attributes {
None => {}
Some(attr_map) => {
for (k, v) in attr_map.iter() {
let n: String = k.clone().into();
Element::check_attr_name(n.as_str())?;
attrs.insert(n, v.clone().into());
}
}
}
let mut xmlns = xmlns;
if xmlns.is_none() {
match &xmlns_prefix {
None => {
xmlns = match attrs.get("xmlns"){
None => None,
Some(ns) => Some(ns.to_string())
}
},
Some(prefix) => {
xmlns = match attrs.get(&format!("xmlns:{prefix}")){
None => None,
Some(ns) => Some(ns.to_string())
}
}
};
}
let mut elem = Self {
name: name,
child_nodes: Vec::new(),
xmlns_context: Element::xmlns_context_from_attributes(&attrs),
attributes: attrs,
xmlns: xmlns.map(|s| s.to_string()),
xmlns_prefix: xmlns_prefix.map(|s| s.to_string())
};
match text {
None => {}
Some(t) => elem.append(Text::new(t))
}
match children {
None => {},
Some(child_vec) => elem.append_all(child_vec)
};
return Ok(elem);
}
pub fn new_from_name(name: &str) -> Result<Self, KissXmlError> {
Element::check_elem_name(name)?;
Ok(Self {
name: name.to_string(),
..Default::default()
})
}
pub fn new_with_attributes<TEXT1: Into<String>+Clone, TEXT2: Into<String>+Clone>(name: &str, attributes: HashMap<TEXT1, TEXT2>) -> Result<Self, KissXmlError> {
Self::new(name, None, Some(attributes), None, None, None)
}
pub fn new_with_text(name: &str, text: impl Into<String>) -> Result<Self, KissXmlError> {
Self::new(name, Some(text.into()), Option::<HashMap<String,String>>::None, None, None, None)
}
pub fn new_with_attributes_and_text<TEXT1: Into<String>+Clone, TEXT2: Into<String>+Clone>(name: &str, attributes: HashMap<TEXT1, TEXT2>, text: impl Into<String>) -> Result<Self, KissXmlError> {
Self::new(name, Some(text.into()), Some(attributes), None, None, None)
}
pub fn new_with_attributes_and_children<TEXT1: Into<String>+Clone, TEXT2: Into<String>+Clone>(name: &str, attributes: HashMap<TEXT1, TEXT2>, children: Vec<Box<dyn Node>>) -> Result<Self, KissXmlError> {
Self::new(name, None, Some(attributes), None, None, Some(children))
}
pub fn new_with_children(name: &str, children: Vec<Box<dyn Node>>) -> Result<Self, KissXmlError> {
Self::new(name, None, Option::<HashMap<String,String>>::None, None, None, Some(children))
}
fn xmlns_context_from_attributes(attrs: &HashMap<String, String>) -> HashMap<String, String> {
let mut prefixes: HashMap<String, String> = HashMap::new();
for (k, v) in attrs.iter() {
let key = k.as_str();
if key.starts_with("xmlns:") {
let split: Vec<&str> = key.splitn(2, ":").collect();
let prefix = split[1].to_string();
let ns = v.clone();
prefixes.insert(prefix, ns);
}
}
return prefixes;
}
pub fn name(&self) -> String {
self.name.clone()
}
pub fn namespace(&self) -> Option<String> {
self.xmlns.clone()
}
pub fn default_namespace(&self) -> Option<String> {
match self.xmlns_prefix{
None => self.xmlns.clone(),
Some(_) => None
}
}
pub fn tag_name(&self) -> String {
match &self.xmlns_prefix{
None => self.name.clone(),
Some(prefix) => format!("{}:{}", prefix, self.name)
}
}
pub fn namespace_prefix(&self) -> Option<String> {
self.xmlns_prefix.clone()
}
pub fn elements_by_namespace(&self, namespace: Option<&str>) -> impl Iterator<Item = &Element>{
let ns = namespace.map(|s| s.to_string());
self.child_elements().filter(move |c| c.xmlns == ns)
}
pub fn elements_by_namespace_mut(&mut self, namespace: Option<&str>) -> impl Iterator<Item = &mut Element>{
let ns = namespace.map(|s| s.to_string());
self.child_elements_mut().filter(move |c| c.xmlns == ns)
}
pub fn elements_by_namespace_prefix(&self, prefix: Option<&str>) -> impl Iterator<Item = &Element>{
let pfx = prefix.map(|p| p.to_string());
self.child_elements().filter(move |c| c.xmlns_prefix == pfx)
}
pub fn elements_by_namespace_prefix_mut(&mut self, prefix: Option<&str>) -> impl Iterator<Item = &mut Element>{
let pfx = prefix.map(|p| p.to_string());
self.child_elements_mut().filter(move |c| c.xmlns_prefix == pfx)
}
pub fn namespace_prefixes(&self) -> Option<HashMap<String, String>> {
let prefixes = Self::xmlns_context_from_attributes(&self.attributes);
if prefixes.is_empty() {
None
} else {
Some(prefixes)
}
}
pub(crate) fn get_namespace_context(&self) -> HashMap<String, String> {self.xmlns_context.clone()}
pub(crate) fn set_namespace_context(&mut self, parent_default_namespace: Option<String>, parent_prefixes: Option<HashMap<String, String>>) {
match self.xmlns_prefix {
None => {
match self.default_namespace() {
None => self.xmlns = parent_default_namespace,
Some(_) => {}
}
}
Some(_) => {}
}
match parent_prefixes {
None => {}
Some(prefixes) => {
for (prefix, ns) in prefixes {
if ! self.xmlns_context.contains_key(prefix.as_str()) {
let _ = &self.xmlns_context.insert(prefix, ns);
}
}
}
}
if self.xmlns.is_none() {
match &self.xmlns_prefix {
None => {}
Some(prefix) => {
self.xmlns = self.xmlns_context.get(prefix).map(String::clone);
}
};
}
}
pub(crate) fn reverse_children(&mut self) {
self.child_nodes.reverse();
}
pub fn child_elements(&self) -> impl Iterator<Item = &Element>{
self.child_nodes.iter()
.filter(|n| n.is_element())
.map(|n| n.as_element().expect("logic error"))
}
pub fn child_elements_mut(&mut self) -> impl Iterator<Item = &mut Element>{
self.child_nodes.iter_mut()
.filter(|n| n.is_element())
.map(|n| n.as_element_mut().expect("logic error"))
}
pub fn children(&self) -> impl Iterator<Item = &Box<dyn Node>>{
self.child_nodes.iter()
}
pub fn all_children(&self) -> impl Iterator<Item = &Box<dyn Node>>{
self.search(|_| true)
}
pub fn children_mut(&mut self) -> impl Iterator<Item = &mut Box<dyn Node>>{
self.child_nodes.iter_mut()
}
pub fn children_recursive(&self) -> Box<dyn Iterator<Item = &Box<dyn Node>> + '_> {
Box::new(
self.child_nodes.iter()
.chain(
self.child_elements().map(|e| e.children_recursive()
).flatten()
)
)
}
pub fn clear_children(&mut self) {self.child_nodes.clear()}
pub fn set_text(&mut self, text: impl Into<String>) {
self.clear_children();
self.append(Text::new(text));
}
pub fn first_element_by_name(&self, name: impl Into<String>) -> Result<&Element, DoesNotExistError> {
let n: String = name.into();
for e in self.child_elements() {
if e.name() == n {
return Ok(e);
}
}
Err(DoesNotExistError::default())
}
pub fn first_element_by_name_mut(&mut self, name: impl Into<String>) -> Result<&mut Element, DoesNotExistError> {
let n: String = name.into();
for e in self.child_elements_mut() {
if e.name() == n {
return Ok(e);
}
}
Err(DoesNotExistError::default())
}
pub fn elements_by_name(&self, name: impl Into<String>) -> impl Iterator<Item = &Element>{
let n: String = name.into();
self.child_elements().filter(move |c| c.name == n)
}
pub fn elements_by_name_mut(&mut self, name: impl Into<String>) -> impl Iterator<Item = &mut Element>{
let n: String = name.into();
self.child_elements_mut().filter(move |c| c.name == n)
}
pub fn attributes(&self) -> &HashMap<String, String> {
&self.attributes
}
pub fn get_attr(&self, attr_name: impl Into<String>) -> Option<&String> {
let n: String = attr_name.into();
self.attributes.get(&n)
}
pub fn set_attr(&mut self, attr_name: impl Into<String>, value: impl Into<String>) -> Result<(), InvalidAttributeName> {
let n: String = attr_name.into();
Element::check_attr_name(n.as_str())?;
let v: String = value.into();
self.attributes.insert(n, v);
Ok(())
}
const ATTR_NAME_CHECKER_SINGLETON: OnceCell<Regex> = OnceCell::new();
fn check_attr_name(name: &str) -> Result<(), InvalidAttributeName> {
let singleton = Element::ATTR_NAME_CHECKER_SINGLETON;
let checker = singleton.get_or_init(
|| Regex::new(r#"^[_a-zA-Z]\S*$"#).unwrap()
);
if checker.is_match(name) {
Ok(())
} else {
Err(InvalidAttributeName::new(format!("'{}' is not a valid attribute name", name)))
}
}
const NAME_CHECKER_SINGLETON: OnceCell<Regex> = OnceCell::new();
fn check_elem_name(name: &str) -> Result<(), InvalidElementName> {
let singleton = Element::NAME_CHECKER_SINGLETON;
let checker = singleton.get_or_init(
|| Regex::new(r#"^[_a-zA-Z]\S*$"#).unwrap()
);
if checker.is_match(name) {
Ok(())
} else {
Err(InvalidElementName::new(format!("'{}' is not a valid name", name)))
}
}
pub fn remove_attr(&mut self, attr_name: impl Into<String>) -> Option<String> {
let n: String = attr_name.into();
self.attributes.remove(&n)
}
pub fn clear_attributes(&mut self) {
self.attributes.clear()
}
pub fn search<'a, P>(&'a self, predicate: P) -> Box<dyn Iterator<Item = &'a Box<dyn Node>> + 'a> where P: FnMut(&&Box<dyn Node>) -> bool + 'a {
Box::new(
self.children_recursive().filter(predicate)
)
}
pub fn search_elements<'a, P>(&'a self, predicate: P) -> Box<dyn Iterator<Item = &'a Element> + 'a> where P: FnMut(&&Element) -> bool + 'a {
Box::new(
self.search(|n| n.is_element())
.map(|n| n.as_element().expect("logic error"))
.filter(predicate)
)
}
pub fn search_elements_by_name(&self, name: impl Into<String>) -> impl Iterator<Item = &Element>{
let n: String = name.into();
self.search_elements(move |e| e.name() == n)
}
pub fn search_text<'a, P>(&'a self, predicate: P) -> Box<dyn Iterator<Item = &'a Text> + 'a> where P: Fn(&&Text) -> bool + 'a {
Box::new(
self.search(|n| n.is_text())
.map(|n| n.as_text().expect("logic error"))
.filter(predicate)
)
}
pub fn search_comments<'a, P>(&'a self, predicate: P) -> Box<dyn Iterator<Item = &'a Comment> + 'a> where P: Fn(&&Comment) -> bool + 'a {
Box::new(
self.search(|n| n.is_comment())
.map(|n| n.as_comment().expect("logic error"))
.filter(predicate)
)
}
pub fn append(&mut self, node: impl Node) {
self.append_boxed(node.boxed());
}
pub fn append_boxed(&mut self, mut node: Box<dyn Node>) {
Self::apply_xmlns_context_to_child_node(self.default_namespace(), self.xmlns_context.clone(), &mut node);
self.child_nodes.push(node);
self.cleanup_text_nodes();
}
fn apply_xmlns_context_to_child_node(df_xmlns: Option<String>, xmlns_context: HashMap<String, String>, node: &mut Box<dyn Node>) {
let is_element = node.is_element();
if is_element {
Self::apply_xmlns_context_to_child_element(
df_xmlns, xmlns_context,
node.as_element_mut().expect("logic error")
);
}
}
fn apply_xmlns_context_to_child_element(df_xmlns: Option<String>, xmlns_context: HashMap<String, String>, child: &mut Element) {
child.set_namespace_context(
df_xmlns,
Some(xmlns_context)
);
}
fn cleanup_text_nodes(&mut self) {
if self.child_nodes.len() == 0 {return;}
let mut index = self.child_nodes.len() - 1;
while index > 0 {
if self.child_nodes[index].is_text()
&& self.child_nodes[index-1].is_text() {
let back = self.child_nodes.remove(index);
let front = self.child_nodes.remove(index-1);
let merged = Text::concat(front.as_text().expect("logic error"), back.as_text().expect("logic error"));
self.child_nodes.insert(index-1, merged.boxed());
}
index -= 1;
}
assert!(self.child_nodes.len() > 0, "logic error: self.child_nodes should never be empty here!");
let mut index = self.child_nodes.len() - 1;
loop {
if self.child_nodes[index].is_text()
&& self.child_nodes[index]
.as_text().expect("logic error").is_whitespace() {
self.child_nodes.remove(index);
}
if index == 0 {break;}
index = index.wrapping_sub(1);
}
}
pub fn append_all(&mut self, children: Vec<Box<dyn Node>>) {
let mut elem_indices: Vec<usize> = Vec::with_capacity(children.len());
let mut i = self.child_nodes.len();
for child in children {
if child.is_element() {
elem_indices.push(i);
}
self.child_nodes.push(child);
i += 1;
}
for i in elem_indices {
Self::apply_xmlns_context_to_child_node(
self.default_namespace(), self.xmlns_context.clone(),
&mut self.child_nodes[i]
);
}
self.cleanup_text_nodes();
}
pub fn insert(&mut self, index: usize, node: impl Node) -> Result<(), IndexOutOfBounds> {
if index > self.child_nodes.len() {
return Err(IndexOutOfBounds::new(index as isize, Some((0, self.child_nodes.len() as isize))));
}
self.child_nodes.insert(index, node.boxed());
Self::apply_xmlns_context_to_child_node(
self.default_namespace(), self.xmlns_context.clone(),
self.child_nodes.last_mut().unwrap()
);
self.cleanup_text_nodes();
Ok(())
}
pub fn remove(&mut self, index: usize) -> Result<Box<dyn Node>, IndexOutOfBounds> {
if index > self.child_nodes.len() {
return Err(IndexOutOfBounds::new(index as isize, Some((0, self.child_nodes.len() as isize))));
}
Ok(self.child_nodes.remove(index))
}
pub fn remove_all<P>(&mut self, predicate: &P) -> usize where P: Fn(&Box<dyn Node>) -> bool {
let mut count = self.remove_by(predicate);
for e in self.child_elements_mut() {
count += e.remove_all(predicate);
}
return count;
}
pub fn remove_by<P>(&mut self, predicate: &P) -> usize where P: Fn(&Box<dyn Node>) -> bool {
let mut rm_indices: Vec<usize> = Vec::new();
for i in (0..self.child_nodes.len()).rev() {
if predicate(&self.child_nodes[i]) {
rm_indices.push(i);
}
}
let count = rm_indices.len();
for i in rm_indices {
self.child_nodes.remove(i);
}
return count;
}
pub fn remove_element(&mut self, index: usize) -> Result<Element, IndexOutOfBounds> {
let mut elems: Vec<usize> = Vec::new();
for i in 0..self.child_nodes.len() {
if self.child_nodes[i].is_element(){ elems.push(i); }
}
if index >= elems.len() {
return Err(IndexOutOfBounds::new(index as isize, Some((0, elems.len() as isize))));
}
let removed = self.child_nodes.remove(elems[index]);
Ok(removed.as_element().expect("logic error").clone())
}
pub fn remove_elements<P>(&mut self, predicate: P) -> usize where P: Fn(&Element) -> bool {
let mut rm_indices: Vec<usize> = Vec::new();
for i in (0..self.child_nodes.len()).rev() {
if self.child_nodes[i].is_element() {
if predicate(
self.child_nodes[i].as_element().expect("logic error")
) {
rm_indices.push(i);
}
}
}
let count = rm_indices.len();
for i in rm_indices {
self.child_nodes.remove(i);
}
return count;
}
pub fn remove_all_elements<P>(&mut self, predicate: P) -> usize where P: Fn(&Element) -> bool {
let new_pred = |n: &Box<dyn Node>| {
match n.is_element(){
true => predicate(n.as_element().unwrap()),
false => false
}
};
let mut count = self.remove_by(&new_pred);
for e in self.child_elements_mut() {
count += e.remove_all(&new_pred);
}
return count;
}
pub fn remove_elements_by_name(&mut self, name: impl Into<String>) -> usize {
let n: String = name.into();
self.remove_elements(move |e| e.name == n)
}
fn to_string_with_prefix_and_indent(&self, prefix: &str, indent: &str, mut inline: bool) -> String {
let mut out = String::new();
if !inline {out.push_str(prefix)}
let tag_name = self.tag_name();
out.push_str("<");
out.push_str(tag_name.as_str());
let mut attrs: Vec<(&String, &String)> = self.attributes().iter().map(|kv| (kv.0, kv.1)).collect();
attrs.sort_by(crate::attribute_order); for (k, v) in attrs {
out.push_str(" ");
out.push_str(k.as_str());
out.push_str("=\"");
out.push_str(crate::attribute_escape(v).as_str());
out.push_str("\"");
}
let child_count = self.child_nodes.len();
if child_count == 0 {
out.push_str("/>");
} else if child_count == 1 && !self.child_nodes[0].is_element() {
out.push_str(">");
out.push_str(&self.child_nodes[0].to_string_with_indent(""));
out.push_str("</");
out.push_str(tag_name.as_str());
out.push_str(">");
} else {
out.push('>');
inline = inline || self.child_nodes.iter().any(|n| n.is_text());
if !inline{out.push('\n');}
let mut next_prefix = String::from(prefix);
next_prefix.push_str(indent);
for c in &self.child_nodes {
if c.is_text() {
let text = crate::text_escape(c.text());
out.push_str(text.as_str());
} else if c.is_element() {
out.push_str(
c.as_element().expect("logic error")
.to_string_with_prefix_and_indent(next_prefix.as_str(), indent, inline).as_str()
);
} else {
if !(inline) {out.push_str(next_prefix.as_str());}
out.push_str(c.to_string_with_indent(indent).as_str());
}
if !inline {out.push('\n');}
}
if !inline {out.push_str(prefix);}
out.push_str("</");
out.push_str(tag_name.as_str());
out.push_str(">");
}
return out;
}
}
impl Node for Element {
fn text(&self) -> String {
let mut builder = String::new();
for c in &self.child_nodes {
if c.is_text() || c.is_element() {
builder.push_str(c.text().as_str())
}
}
builder
}
fn is_element(&self) -> bool {
true
}
fn is_text(&self) -> bool {
false
}
fn is_comment(&self) -> bool {
false
}
fn is_cdata(&self) -> bool {
false
}
fn as_element(&self) -> Result<&Element, TypeCastError> {Ok(&self)}
fn as_comment(&self) -> Result<&Comment, TypeCastError> {Err(TypeCastError::new("Cannot cast Element as Comment"))}
fn as_text(&self) -> Result<&Text, TypeCastError> {Err(TypeCastError::new("Cannot cast Element as Text"))}
fn as_cdata(&self) -> Result<&CData, TypeCastError> {Err(TypeCastError::new("Cannot cast Element as CData"))}
fn as_element_mut(&mut self) -> Result<&mut Element, TypeCastError> {Ok(self)}
fn as_comment_mut(&mut self) -> Result<&mut Comment, TypeCastError> {Err(TypeCastError::new("Cannot cast Element as Comment"))}
fn as_text_mut(&mut self) -> Result<&mut Text, TypeCastError> {Err(TypeCastError::new("Cannot cast Element as Text"))}
fn as_cdata_mut(&mut self) -> Result<&mut CData, TypeCastError> {Err(TypeCastError::new("Cannot cast Element as CData"))}
fn as_node(&self) -> &dyn Node {self}
fn as_node_mut(&mut self) -> &mut dyn Node {self}
fn as_any(&self) -> &dyn Any {self}
fn as_any_mut(&mut self) -> &mut dyn Any{self}
fn to_string_with_indent(&self, indent: &str) -> String {
match crate::validate_indent(indent){
Ok(_) => self.to_string_with_prefix_and_indent("", indent, false),
Err(_) => {
eprintln!("WARNING: {:?} is not a valid indentation. Must be either 1 tab or any number of spaces. The default of 2 spaces will be used instead", indent);
self.to_string_with_prefix_and_indent("", " ", false)
}
}
}
fn boxed(self) -> Box<dyn Node> {
Box::new(self)
}
}
impl Clone for Element {
fn clone(&self) -> Self {
let mut new_children: Vec<Box<dyn Node>> = Vec::with_capacity(self.child_nodes.len());
for c in &self.child_nodes {
new_children.push(clone_node(c))
}
Self {
name: self.name.clone(),
child_nodes: new_children,
attributes: self.attributes.clone(),
xmlns: self.xmlns.clone(),
xmlns_prefix: self.xmlns_prefix.clone(),
xmlns_context: self.xmlns_context.clone(),
}
}
}
impl Default for Element {
fn default() -> Self {
Self {
name: "x".to_string(),
child_nodes: Vec::new(),
attributes: Default::default(),
xmlns: None,
xmlns_prefix: None,
xmlns_context: HashMap::new(),
}
}
}
impl PartialOrd for Element {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
self.name.partial_cmp(&other.name)
}
}
impl PartialEq<Self> for Element {
fn eq(&self, other: &Self) -> bool {
if self.name == other.name && self.xmlns == other.xmlns
&& self.xmlns_prefix == other.xmlns_prefix
&& self.attributes == other.attributes
&& self.child_nodes.len() == other.child_nodes.len() {
for i in 0..self.child_nodes.len() {
if !node_eq(&self.child_nodes[i], &other.child_nodes[i]) {
return false;
}
}
return true;
}
return false;
}
}
impl Hash for Element {
fn hash<H: Hasher>(&self, state: &mut H) {
self.name.hash(state);
self.xmlns.hash(state);
}
}
impl std::fmt::Display for Element {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.to_string_with_indent(" "))
}
}
impl std::fmt::Debug for Element {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.to_string_with_indent(" "))
}
}
#[derive(Clone)]
pub struct Text {
pub content: String
}
const WSP_MATCHER_SINGLETON: OnceCell<Regex> = OnceCell::new();
impl Text {
pub fn new(text: impl Into<String>) -> Self {
let content: String = text.into();
Self{content}
}
pub fn concat(&self, other: &Text) -> Text {
let mut content = String::new();
content.push_str(self.content.as_str());
content.push_str(other.content.as_str());
Text{content}
}
fn is_whitespace(&self) -> bool {
let singleton = WSP_MATCHER_SINGLETON;
let wsp_matcher = singleton.get_or_init(|| Regex::new(r#"^\s+$"#).unwrap());
wsp_matcher.is_match(self.content.as_str())
}
}
impl From<&str> for Text {
fn from(value: &str) -> Self {
Text::new(value)
}
}
impl From<String> for Text {
fn from(value: String) -> Self {
Text::new(value)
}
}
impl Node for Text {
fn text(&self) -> String {
self.content.clone()
}
fn is_element(&self) -> bool {
false
}
fn is_text(&self) -> bool {
true
}
fn is_comment(&self) -> bool {
false
}
fn is_cdata(&self) -> bool {
false
}
fn as_element(&self) -> Result<&Element, TypeCastError> {Err(TypeCastError::new("Cannot cast Text as Element"))}
fn as_comment(&self) -> Result<&Comment, TypeCastError> {Err(TypeCastError::new("Cannot cast Text as Comment"))}
fn as_text(&self) -> Result<&Text, TypeCastError> {Ok(&self)}
fn as_cdata(&self) -> Result<&CData, TypeCastError> {Err(TypeCastError::new("Cannot cast Text as CData"))}
fn as_element_mut(&mut self) -> Result<&mut Element, TypeCastError> {Err(TypeCastError::new("Cannot cast Text as Element"))}
fn as_comment_mut(&mut self) -> Result<&mut Comment, TypeCastError> {Err(TypeCastError::new("Cannot cast Text as Comment"))}
fn as_text_mut(&mut self) -> Result<&mut Text, TypeCastError> {Ok(self)}
fn as_cdata_mut(&mut self) -> Result<&mut CData, TypeCastError> {Err(TypeCastError::new("Cannot cast Text as CData"))}
fn as_node(&self) -> &dyn Node {self}
fn as_node_mut(&mut self) -> &mut dyn Node {self}
fn as_any(&self) -> &dyn Any {self}
fn as_any_mut(&mut self) -> &mut dyn Any{self}
fn to_string_with_indent(&self, _indent: &str) -> String {
crate::text_escape(self.content.clone())
}
fn boxed(self) -> Box<dyn Node> {
Box::new(self)
}
}
impl PartialOrd for Text {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
self.content.partial_cmp(&other.content)
}
}
impl PartialEq<Self> for Text {
fn eq(&self, other: &Self) -> bool {
self.content.eq(&other.content)
}
}
impl Hash for Text {
fn hash<H: Hasher>(&self, state: &mut H) {
self.content.hash(state)
}
}
impl std::fmt::Display for Text {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.to_string_with_indent(" "))
}
}
impl std::fmt::Debug for Text {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.to_string_with_indent(" "))
}
}
#[derive(Clone)]
pub struct Comment{
comment: String
}
impl Comment {
pub fn new(comment: impl Into<String>) -> Result<Self, InvalidContent> {
let content: String = comment.into();
if content.contains("-->") {
Err(InvalidContent::new("Comments cannot contain '-->'"))
} else {
Ok(Self { comment: content })
}
}
pub fn get_content(&self) -> &str {
self.comment.as_str()
}
pub fn set_content(&mut self, content: impl Into<String>) -> Result<(), InvalidContent> {
let content = content.into();
if content.contains("-->") {
Err(InvalidContent::new("Comments cannot contain '-->'"))
} else {
self.comment = content.into();
Ok(())
}
}
}
impl Node for Comment {
fn text(&self) -> String {
self.comment.clone()
}
fn is_element(&self) -> bool {
false
}
fn is_text(&self) -> bool {
false
}
fn is_comment(&self) -> bool {
true
}
fn is_cdata(&self) -> bool {
false
}
fn as_element(&self) -> Result<&Element, TypeCastError> {Err(TypeCastError::new("Cannot cast Comment as Element"))}
fn as_comment(&self) -> Result<&Comment, TypeCastError> {Ok(&self)}
fn as_text(&self) -> Result<&Text, TypeCastError> {Err(TypeCastError::new("Cannot cast Comment as Text"))}
fn as_cdata(&self) -> Result<&CData, TypeCastError> {Err(TypeCastError::new("Cannot cast Comment as CData"))}
fn as_element_mut(&mut self) -> Result<&mut Element, TypeCastError> {Err(TypeCastError::new("Cannot cast Comment as Element"))}
fn as_comment_mut(&mut self) -> Result<&mut Comment, TypeCastError> {Ok(self)}
fn as_text_mut(&mut self) -> Result<&mut Text, TypeCastError> {Err(TypeCastError::new("Cannot cast Comment as Text"))}
fn as_cdata_mut(&mut self) -> Result<&mut CData, TypeCastError> {Err(TypeCastError::new("Cannot cast Comment as CData"))}
fn as_node(&self) -> &dyn Node {self}
fn as_node_mut(&mut self) -> &mut dyn Node {self}
fn as_any(&self) -> &dyn Any {self}
fn as_any_mut(&mut self) -> &mut dyn Any{self}
fn to_string_with_indent(&self, _indent: &str) -> String {
format!("<!--{}-->", self.comment)
}
fn boxed(self) -> Box<dyn Node> {
Box::new(self)
}
}
impl From<&str> for Comment {
fn from(value: &str) -> Self {
Comment::new(value).unwrap()
}
}
impl From<String> for Comment {
fn from(value: String) -> Self {
Comment::new(value).unwrap()
}
}
impl PartialOrd for Comment {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
self.comment.partial_cmp(&other.comment)
}
}
impl PartialEq<Self> for Comment {
fn eq(&self, other: &Self) -> bool {
self.comment.eq(&other.comment)
}
}
impl Hash for Comment {
fn hash<H: Hasher>(&self, state: &mut H) {
self.comment.hash(state)
}
}
impl std::fmt::Display for Comment {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.to_string_with_indent(" "))
}
}
impl std::fmt::Debug for Comment {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.to_string_with_indent(" "))
}
}
#[derive(Clone)]
pub struct CData{
cdata: String
}
impl CData {
pub fn new(cdata: impl Into<String>) -> Result<Self, InvalidContent> {
let content: String = cdata.into();
if content.contains("]]>") {
Err(InvalidContent::new("CDATA cannot contain ']]>' as content"))
} else {
Ok(Self { cdata: content })
}
}
pub fn set_text(&mut self, content: impl Into<String>) -> Result<(), InvalidContent> {
let content = content.into();
if content.contains("]]>") {
Err(InvalidContent::new("CDATA cannot contain ']]>'"))
} else {
self.cdata = content.into();
Ok(())
}
}
pub fn get_content(&self) -> &str {
self.cdata.as_str()
}
}
impl Node for CData {
fn text(&self) -> String {
self.cdata.clone()
}
fn is_element(&self) -> bool {
false
}
fn is_text(&self) -> bool {
false
}
fn is_comment(&self) -> bool {
false
}
fn is_cdata(&self) -> bool {
true
}
fn as_element(&self) -> Result<&Element, TypeCastError> {Err(TypeCastError::new("Cannot cast CData as Element"))}
fn as_comment(&self) -> Result<&Comment, TypeCastError> {Err(TypeCastError::new("Cannot cast CData as Comment"))}
fn as_text(&self) -> Result<&Text, TypeCastError> {Err(TypeCastError::new("Cannot cast CData as Text"))}
fn as_cdata(&self) -> Result<&CData, TypeCastError> {Ok(&self)}
fn as_element_mut(&mut self) -> Result<&mut Element, TypeCastError> {Err(TypeCastError::new("Cannot cast CData as Element"))}
fn as_comment_mut(&mut self) -> Result<&mut Comment, TypeCastError> {Err(TypeCastError::new("Cannot cast CData as Comment"))}
fn as_text_mut(&mut self) -> Result<&mut Text, TypeCastError> {Err(TypeCastError::new("Cannot cast CData as Text"))}
fn as_cdata_mut(&mut self) -> Result<&mut CData, TypeCastError> {Ok(self)}
fn as_node(&self) -> &dyn Node {self}
fn as_node_mut(&mut self) -> &mut dyn Node {self}
fn as_any(&self) -> &dyn Any {self}
fn as_any_mut(&mut self) -> &mut dyn Any{self}
fn to_string_with_indent(&self, _indent: &str) -> String {
format!("<![CDATA[{}]]>", self.cdata)
}
fn boxed(self) -> Box<dyn Node> {
Box::new(self)
}
}
impl From<&str> for CData {
fn from(value: &str) -> Self {
CData::new(value).unwrap()
}
}
impl From<String> for CData {
fn from(value: String) -> Self {
CData::new(value).unwrap()
}
}
impl PartialOrd for CData {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
self.cdata.partial_cmp(&other.cdata)
}
}
impl PartialEq<Self> for CData {
fn eq(&self, other: &Self) -> bool {
self.cdata.eq(&other.cdata)
}
}
impl Hash for CData {
fn hash<H: Hasher>(&self, state: &mut H) {
self.cdata.hash(state)
}
}
impl std::fmt::Display for CData {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.to_string_with_indent(" "))
}
}
impl std::fmt::Debug for CData {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.to_string_with_indent(" "))
}
}
#[derive(Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
pub struct Declaration {
decl_str: String
}
impl Declaration {
pub fn from_str(decl: &str) -> Result<Self, KissXmlError> {
let buffer: String = decl.trim().to_string();
if buffer.starts_with("<?") && buffer.ends_with("?>"){
Ok(Self{decl_str: buffer.strip_prefix("<?").unwrap().strip_suffix("?>").unwrap().to_string()})
} else {
Err(ParsingError::new("Invalid XML declaration syntax").into())
}
}
pub fn new() -> Self {
Self::default()
}
}
impl Default for Declaration {
fn default() -> Self {
Declaration::from_str(r#"<?xml version="1.0" encoding="UTF-8"?>"#).unwrap()
}
}
impl std::fmt::Display for Declaration {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "<?{}?>", self.decl_str)
}
}
impl std::fmt::Debug for Declaration {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "<?{}?>", self.decl_str)
}
}
#[derive(Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
pub struct DTD {
dtd_str: String
}
impl DTD {
pub fn from_string(text: impl Into<String>) -> Result<DTD, KissXmlError> {
let buffer: String = text.into().trim().to_string();
if buffer.starts_with("<!DOCTYPE") && buffer.ends_with(">"){
Ok(Self{dtd_str: buffer.strip_prefix("<!DOCTYPE").unwrap().strip_suffix(">").unwrap().to_string()})
} else {
Err(ParsingError::new("Invalid DTD syntax").into())
}
}
}
impl std::fmt::Display for DTD {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.dtd_str)
}
}