use std::fmt::{Debug, Display};
use enum_extract_macro::EnumExtract;
use indexmap::IndexMap;
use indextree::{Arena, NodeId};
use ordered_float::OrderedFloat;
use crate::xpath::ExpressionApplyError;
use super::{
expressions::Expr, DisplayFormatting, TextIter, XpathItemTree, XpathItemTreeNode,
VOID_ELEMENTS,
};
fn escape_attribute_value(value: &str) -> String {
value
.replace('&', "&")
.replace('"', """)
.replace('<', "<")
.replace('>', ">")
}
fn escape_text_content(text: &str) -> String {
text.replace('&', "&")
.replace('<', "<")
.replace('>', ">")
}
#[derive(PartialEq, Eq, Debug, Clone, Hash, EnumExtract)]
pub enum XpathItem<'tree> {
Node(&'tree XpathItemTreeNode),
Function(Function),
AnyAtomicType(AnyAtomicType),
}
impl<'tree> From<&'tree XpathItemTreeNode> for XpathItem<'tree> {
fn from(node: &'tree XpathItemTreeNode) -> Self {
XpathItem::Node(node)
}
}
#[derive(Debug, Clone)]
pub enum AnyAtomicType {
Boolean(bool),
Integer(i64),
Float(OrderedFloat<f32>),
Double(OrderedFloat<f64>),
String(String),
QName {
namespace_uri: String,
local_name: String,
prefix: Option<String>,
},
}
impl PartialEq for AnyAtomicType {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(AnyAtomicType::Boolean(a), AnyAtomicType::Boolean(b)) => a == b,
(AnyAtomicType::Integer(a), AnyAtomicType::Integer(b)) => a == b,
(AnyAtomicType::Float(a), AnyAtomicType::Float(b)) => a == b,
(AnyAtomicType::Double(a), AnyAtomicType::Double(b)) => a == b,
(AnyAtomicType::String(a), AnyAtomicType::String(b)) => a == b,
(
AnyAtomicType::QName {
namespace_uri: ns1,
local_name: ln1,
..
},
AnyAtomicType::QName {
namespace_uri: ns2,
local_name: ln2,
..
},
) => ns1 == ns2 && ln1 == ln2,
_ => false,
}
}
}
impl Eq for AnyAtomicType {}
impl std::hash::Hash for AnyAtomicType {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
std::mem::discriminant(self).hash(state);
match self {
AnyAtomicType::Boolean(b) => b.hash(state),
AnyAtomicType::Integer(i) => i.hash(state),
AnyAtomicType::Float(f) => f.hash(state),
AnyAtomicType::Double(d) => d.hash(state),
AnyAtomicType::String(s) => s.hash(state),
AnyAtomicType::QName {
namespace_uri,
local_name,
..
} => {
namespace_uri.hash(state);
local_name.hash(state);
}
}
}
}
fn compare_integer_double(int_val: i64, dbl_val: f64) -> Option<std::cmp::Ordering> {
if dbl_val.is_nan() {
return None;
}
if dbl_val.is_infinite() {
return if dbl_val > 0.0 {
Some(std::cmp::Ordering::Less)
} else {
Some(std::cmp::Ordering::Greater)
};
}
let trunc = dbl_val.trunc();
if dbl_val != trunc {
if trunc >= i64::MIN as f64 && trunc <= i64::MAX as f64 {
let trunc_int = trunc as i64;
match int_val.cmp(&trunc_int) {
std::cmp::Ordering::Equal => {
if dbl_val > trunc {
Some(std::cmp::Ordering::Less) } else {
Some(std::cmp::Ordering::Greater) }
}
other => Some(other),
}
} else {
OrderedFloat(int_val as f64).partial_cmp(&OrderedFloat(dbl_val))
}
} else {
if trunc >= i64::MIN as f64 && trunc <= i64::MAX as f64 {
let dbl_as_int = trunc as i64;
Some(int_val.cmp(&dbl_as_int))
} else {
OrderedFloat(int_val as f64).partial_cmp(&OrderedFloat(dbl_val))
}
}
}
impl PartialOrd for AnyAtomicType {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
match (self, other) {
(AnyAtomicType::Boolean(a), AnyAtomicType::Boolean(b)) => a.partial_cmp(b),
(AnyAtomicType::Integer(a), AnyAtomicType::Integer(b)) => a.partial_cmp(b),
(AnyAtomicType::Float(a), AnyAtomicType::Float(b)) => a.partial_cmp(b),
(AnyAtomicType::Double(a), AnyAtomicType::Double(b)) => a.partial_cmp(b),
(AnyAtomicType::String(a), AnyAtomicType::String(b)) => a.partial_cmp(b),
(AnyAtomicType::Integer(a), AnyAtomicType::Double(b)) => {
compare_integer_double(*a, b.0)
}
(AnyAtomicType::Double(a), AnyAtomicType::Integer(b)) => {
compare_integer_double(*b, a.0).map(|o| o.reverse())
}
(AnyAtomicType::Integer(a), AnyAtomicType::Float(b)) => {
compare_integer_double(*a, b.0 as f64)
}
(AnyAtomicType::Float(a), AnyAtomicType::Integer(b)) => {
compare_integer_double(*b, a.0 as f64).map(|o| o.reverse())
}
(AnyAtomicType::Float(a), AnyAtomicType::Double(b)) => {
OrderedFloat(a.0 as f64).partial_cmp(b)
}
(AnyAtomicType::Double(a), AnyAtomicType::Float(b)) => {
a.partial_cmp(&OrderedFloat(b.0 as f64))
}
(
AnyAtomicType::QName {
namespace_uri: ns1,
local_name: ln1,
..
},
AnyAtomicType::QName {
namespace_uri: ns2,
local_name: ln2,
..
},
) => (ns1, ln1).partial_cmp(&(ns2, ln2)),
_ => None,
}
}
}
impl Display for AnyAtomicType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
AnyAtomicType::Boolean(b) => write!(f, "{}", b),
AnyAtomicType::Integer(i) => write!(f, "{}", i),
AnyAtomicType::Float(fl) => write!(f, "{}", fl),
AnyAtomicType::Double(d) => write!(f, "{}", d),
AnyAtomicType::String(s) => write!(f, "{}", s),
AnyAtomicType::QName {
prefix: Some(p),
local_name,
..
} => write!(f, "{}:{}", p, local_name),
AnyAtomicType::QName {
local_name,
..
} => write!(f, "{}", local_name),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum OwnedXpathValue {
Atomic(AnyAtomicType),
Function(Box<Function>),
}
impl std::fmt::Display for OwnedXpathValue {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
OwnedXpathValue::Atomic(a) => write!(f, "{}", a),
OwnedXpathValue::Function(func) => write!(f, "{}", func),
}
}
}
impl OwnedXpathValue {
pub fn from_xpath_item(item: &XpathItem<'_>, tree: &XpathItemTree) -> Self {
match item {
XpathItem::AnyAtomicType(a) => OwnedXpathValue::Atomic(a.clone()),
XpathItem::Function(f) => OwnedXpathValue::Function(Box::new(f.clone())),
XpathItem::Node(node) => {
let s = node.text_content(tree);
OwnedXpathValue::Atomic(AnyAtomicType::String(s))
}
}
}
pub fn to_xpath_item<'tree>(&self) -> XpathItem<'tree> {
match self {
OwnedXpathValue::Atomic(a) => XpathItem::AnyAtomicType(a.clone()),
OwnedXpathValue::Function(f) => XpathItem::Function((**f).clone()),
}
}
}
#[derive(Debug, Clone)]
pub enum Function {
Named {
name: String,
arity: u32,
},
Inline {
params: Vec<String>,
body_source: String,
body: Option<Box<Expr>>,
},
Map {
entries: IndexMap<AnyAtomicType, Vec<OwnedXpathValue>>,
},
Array {
members: Vec<Vec<OwnedXpathValue>>,
},
}
impl PartialEq for Function {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(
Function::Named { name: n1, arity: a1 },
Function::Named { name: n2, arity: a2 },
) => n1 == n2 && a1 == a2,
(
Function::Inline { params: p1, body_source: b1, .. },
Function::Inline { params: p2, body_source: b2, .. },
) => p1 == p2 && b1 == b2,
(Function::Map { entries: e1 }, Function::Map { entries: e2 }) => e1 == e2,
(Function::Array { members: m1 }, Function::Array { members: m2 }) => m1 == m2,
_ => false,
}
}
}
impl Eq for Function {}
impl std::hash::Hash for Function {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
std::mem::discriminant(self).hash(state);
match self {
Function::Named { name, arity } => {
name.hash(state);
arity.hash(state);
}
Function::Inline { params, body_source, .. } => {
params.hash(state);
body_source.hash(state);
}
Function::Map { entries } => {
entries.len().hash(state);
for (k, v) in entries {
k.hash(state);
v.hash(state);
}
}
Function::Array { members } => {
members.hash(state);
}
}
}
}
impl Display for Function {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Function::Named { name, arity } => write!(f, "{}#{}", name, arity),
Function::Inline {
params,
body_source,
..
} => {
write!(f, "function(")?;
for (i, param) in params.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "${}", param)?;
}
write!(f, ") {{ {} }}", body_source)
}
Function::Map { entries } => {
write!(f, "map {{")?;
for (i, (key, values)) in entries.iter().enumerate() {
if i == 0 {
write!(f, " ")?;
} else {
write!(f, ", ")?;
}
write!(f, "{}: ", key)?;
if values.len() == 1 {
write!(f, "{}", values[0])?;
} else {
write!(f, "(")?;
for (j, v) in values.iter().enumerate() {
if j > 0 {
write!(f, ", ")?;
}
write!(f, "{}", v)?;
}
write!(f, ")")?;
}
}
write!(f, " }}")
}
Function::Array { members } => {
write!(f, "[")?;
for (i, member) in members.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
if member.len() == 1 {
write!(f, "{}", member[0])?;
} else {
write!(f, "(")?;
for (j, v) in member.iter().enumerate() {
if j > 0 {
write!(f, ", ")?;
}
write!(f, "{}", v)?;
}
write!(f, ")")?;
}
}
write!(f, "]")
}
}
}
}
#[derive(PartialEq, PartialOrd, Eq, Ord, Debug, Hash, Clone)]
pub struct XpathDocumentNode {}
impl XpathDocumentNode {
pub(crate) fn new() -> Self {
Self {}
}
pub fn text_content(&self, tree: &XpathItemTree) -> String {
let strings: Vec<String> = tree
.root_node
.children(&tree.arena)
.map(|x| tree.get(x))
.map(|x| x.text_content(tree))
.collect();
strings.join("")
}
pub fn text(&self, tree: &XpathItemTree) -> Option<String> {
tree.root_node
.children(&tree.arena)
.map(|x| tree.get(x))
.find_map(|x| x.text(tree))
}
pub fn children<'tree>(&self, tree: &'tree XpathItemTree) -> Vec<&'tree XpathItemTreeNode> {
tree.root_node
.children(&tree.arena)
.map(|x| tree.get(x))
.collect()
}
pub fn display(
&self,
tree: &XpathItemTree,
formatting: DisplayFormatting,
) -> String {
match formatting {
DisplayFormatting::Raw => {
let child_strings: Vec<String> = tree
.root_node
.children(&tree.arena)
.map(|x| tree.get(x))
.map(|x| x.display(tree, formatting, 0))
.collect();
child_strings.join("")
}
_ => {
let children = self.children(tree);
let element_strings: Vec<String> = children
.iter()
.filter_map(|x| x.as_element_node().ok())
.map(|x| x.display(tree, formatting, 0))
.collect();
element_strings.join("\n")
}
}
}
}
#[derive(PartialEq, Eq, Hash, Clone)]
pub struct ElementNode {
id: Option<NodeId>,
pub name: String,
pub namespace: Option<String>,
}
impl std::fmt::Debug for ElementNode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ElementNode")
.field("name", &self.name)
.finish()
}
}
impl ElementNode {
pub(crate) fn new(name: String) -> Self {
Self {
id: None,
name,
namespace: None,
}
}
pub(crate) fn set_id(&mut self, id: NodeId) {
self.id = Some(id);
}
pub(crate) fn id(&self) -> Result<NodeId, ExpressionApplyError> {
self.id.ok_or_else(|| ExpressionApplyError::new("BUG: node ID not set -- was set_id() called?".to_string()))
}
pub fn attributes<'tree>(&self, tree: &'tree XpathItemTree) -> Vec<&'tree AttributeNode> {
self.children(tree)
.filter_map(|x| match x {
XpathItemTreeNode::AttributeNode(attr) => Some(attr),
_ => None,
})
.collect()
}
pub(crate) fn attributes_arena<'arena>(
&self,
arena: &'arena Arena<XpathItemTreeNode>,
) -> Vec<&'arena AttributeNode> {
self.children_arena(arena)
.filter_map(|x| match x {
XpathItemTreeNode::AttributeNode(attr) => Some(attr),
_ => None,
})
.collect()
}
pub fn get_attribute<'tree>(
&self,
tree: &'tree XpathItemTree,
name: &str,
) -> Option<&'tree str> {
self.attributes(tree)
.iter()
.find(|x| x.name == name)
.map(|x| &*x.value)
}
pub fn children<'tree>(
&self,
tree: &'tree XpathItemTree,
) -> Box<dyn Iterator<Item = &'tree XpathItemTreeNode> + 'tree> {
match self.id() {
Ok(id) => Box::new(id.children(&tree.arena).map(|x| tree.get(x))),
Err(_) => Box::new(std::iter::empty()),
}
}
pub(crate) fn children_arena<'arena>(
&self,
arena: &'arena Arena<XpathItemTreeNode>,
) -> Box<dyn Iterator<Item = &'arena XpathItemTreeNode> + 'arena> {
match self.id() {
Ok(id) => Box::new(
id.children(arena)
.map(|x| arena.get(x).expect("node missing from arena").get()),
),
Err(_) => Box::new(std::iter::empty()),
}
}
pub fn parent<'tree>(&self, tree: &'tree XpathItemTree) -> Option<&'tree XpathItemTreeNode> {
tree.get(self.id().ok()?).parent(tree)
}
pub fn itertext(&self, tree: &XpathItemTree) -> TextIter {
match self.id() {
Ok(id) => TextIter::new(tree, tree.get(id)),
Err(_) => TextIter::empty(),
}
}
pub fn text_content(&self, tree: &XpathItemTree) -> String {
self.itertext(tree).collect::<Vec<String>>().join("")
}
pub fn text(&self, tree: &XpathItemTree) -> Option<String> {
let mut texts = Vec::new();
for child in self.children(tree) {
match child {
XpathItemTreeNode::TextNode(text) => {
texts.push(text.content.clone());
}
XpathItemTreeNode::AttributeNode(_) => {}
_ => break,
}
}
if texts.is_empty() {
None
} else {
Some(texts.join(""))
}
}
pub fn to_item<'tree>(&self, tree: &'tree XpathItemTree) -> XpathItem<'tree> {
XpathItem::Node(tree.get(self.id().expect("node ID not set")))
}
pub fn display(
&self,
tree: &XpathItemTree,
formatting: DisplayFormatting,
indent: usize,
) -> String {
match formatting {
DisplayFormatting::Raw => self.display_raw(tree),
_ => self.display_pretty(tree, formatting, indent),
}
}
fn display_raw(&self, tree: &XpathItemTree) -> String {
let attributes = self.attributes(tree);
let mut display_string = String::new();
display_string.push_str(&format!("<{}", self.name));
for attr in &attributes {
display_string.push_str(&attr.prefix);
let escaped_value = escape_attribute_value(&attr.value);
let display_name = attr.original_name.as_deref().unwrap_or(&attr.name);
display_string.push_str(&format!("{}=\"{}\"", display_name, escaped_value));
}
display_string.push('>');
let is_void = VOID_ELEMENTS.contains(&self.name.as_str());
if !is_void {
let children_without_attributes =
self.children(tree).filter(|x| !x.is_attribute_node());
for child in children_without_attributes {
display_string.push_str(&child.display(tree, DisplayFormatting::Raw, 0));
}
display_string.push_str(&format!("</{}>", self.name));
}
display_string
}
fn display_pretty(
&self,
tree: &XpathItemTree,
formatting: DisplayFormatting,
indent: usize,
) -> String {
let displayed_children: Vec<String> = match formatting {
DisplayFormatting::Pretty => {
let children_without_attributes =
self.children(tree).filter(|x| !x.is_attribute_node());
children_without_attributes
.map(|x| x.display(tree, formatting, indent + 1))
.filter(|x| !x.trim().is_empty())
.collect()
}
DisplayFormatting::NoChildren => Vec::new(),
DisplayFormatting::Raw => Vec::new(),
};
let attributes = self.attributes(tree);
let displayed_attributes: Vec<String> = attributes.iter().map(|x| x.to_string()).collect();
let indentation = " ".repeat(indent);
let mut display_string = String::new();
if displayed_attributes.is_empty() {
display_string.push_str(&format!("{}<{}>", indentation, self.name,));
} else {
display_string.push_str(&format!(
"{}<{} {}>",
indentation,
self.name,
displayed_attributes.join(" "),
));
}
let is_void = VOID_ELEMENTS.contains(&self.name.as_str());
if !is_void {
if !displayed_children.is_empty() {
display_string.push_str(&format!(
"\n{}\n{}",
&displayed_children.join("\n"),
indentation
));
}
display_string.push_str(&format!("</{}>", self.name));
}
display_string
}
}
#[derive(Eq, Clone)]
pub struct AttributeNode {
id: Option<NodeId>,
pub name: String,
pub value: String,
pub prefix: String,
pub original_name: Option<String>,
pub namespace: Option<String>,
}
impl Debug for AttributeNode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("AttributeNode")
.field("name", &self.name)
.field("value", &self.value)
.finish()
}
}
impl AttributeNode {
pub(crate) fn new(name: String, value: String) -> Self {
Self {
id: None,
name,
value,
prefix: String::from(" "),
original_name: None,
namespace: None,
}
}
pub(crate) fn with_prefix(
name: String,
value: String,
prefix: String,
original_name: Option<String>,
namespace: Option<String>,
) -> Self {
Self {
id: None,
name,
value,
prefix,
original_name,
namespace,
}
}
pub(crate) fn set_id(&mut self, id: NodeId) {
self.id = Some(id);
}
pub(crate) fn id(&self) -> Result<NodeId, ExpressionApplyError> {
self.id.ok_or_else(|| ExpressionApplyError::new("BUG: node ID not set -- was set_id() called?".to_string()))
}
pub fn parent<'tree>(&self, tree: &'tree XpathItemTree) -> Option<&'tree XpathItemTreeNode> {
tree.get(self.id().ok()?).parent(tree)
}
}
impl Display for AttributeNode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}=\"{}\"", self.name, escape_attribute_value(&self.value))
}
}
impl PartialEq for AttributeNode {
fn eq(&self, other: &Self) -> bool {
self.name == other.name && self.value == other.value
}
}
impl std::hash::Hash for AttributeNode {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.name.hash(state);
self.value.hash(state);
}
}
#[derive(Eq, Debug, Clone)]
pub struct PINode {
pub target: String,
pub data: String,
id: Option<NodeId>,
}
impl PINode {
pub(crate) fn new(target: String, data: String) -> Self {
Self {
target,
data,
id: None,
}
}
pub(crate) fn create(
target: String,
data: String,
arena: &mut Arena<XpathItemTreeNode>,
) -> NodeId {
let node_id = arena.new_node(XpathItemTreeNode::PINode(PINode::new(target, data)));
arena
.get_mut(node_id)
.unwrap()
.get_mut()
.as_pi_node_mut()
.unwrap()
.set_id(node_id);
node_id
}
pub(crate) fn set_id(&mut self, id: NodeId) {
self.id = Some(id);
}
pub(crate) fn id(&self) -> Result<NodeId, ExpressionApplyError> {
self.id.ok_or_else(|| ExpressionApplyError::new("BUG: node ID not set -- was set_id() called?".to_string()))
}
}
impl PartialEq for PINode {
fn eq(&self, other: &Self) -> bool {
self.target == other.target && self.data == other.data
}
}
impl PartialOrd for PINode {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for PINode {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
(&self.target, &self.data).cmp(&(&other.target, &other.data))
}
}
impl std::hash::Hash for PINode {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.target.hash(state);
self.data.hash(state);
}
}
impl Display for PINode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.data.is_empty() {
write!(f, "<?{}?>", self.target)
} else {
write!(f, "<?{} {}?>", self.target, self.data)
}
}
}
#[derive(Eq, Debug, Clone)]
pub struct CommentNode {
pub content: String,
id: Option<NodeId>,
}
impl CommentNode {
pub(crate) fn new(content: String) -> Self {
Self { content, id: None }
}
pub(crate) fn create(content: String, arena: &mut Arena<XpathItemTreeNode>) -> NodeId {
let node_id = arena.new_node(XpathItemTreeNode::CommentNode(CommentNode::new(content)));
arena
.get_mut(node_id)
.unwrap()
.get_mut()
.as_comment_node_mut()
.unwrap()
.set_id(node_id);
node_id
}
pub(crate) fn set_id(&mut self, id: NodeId) {
self.id = Some(id);
}
pub(crate) fn id(&self) -> Result<NodeId, ExpressionApplyError> {
self.id.ok_or_else(|| ExpressionApplyError::new("BUG: node ID not set -- was set_id() called?".to_string()))
}
pub fn parent<'tree>(&self, tree: &'tree XpathItemTree) -> Option<&'tree XpathItemTreeNode> {
tree.get(self.id().ok()?).parent(tree)
}
}
impl Display for CommentNode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "<!--{}-->", self.content)
}
}
impl PartialEq for CommentNode {
fn eq(&self, other: &Self) -> bool {
self.content == other.content
}
}
impl PartialOrd for CommentNode {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for CommentNode {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.content.cmp(&other.content)
}
}
impl std::hash::Hash for CommentNode {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.content.hash(state);
}
}
#[derive(Eq, Debug, Clone)]
pub struct DoctypeNode {
pub name: String,
pub public_id: Option<String>,
pub system_id: Option<String>,
id: Option<NodeId>,
}
impl DoctypeNode {
pub(crate) fn new(name: String, public_id: Option<String>, system_id: Option<String>) -> Self {
Self {
name,
public_id,
system_id,
id: None,
}
}
pub(crate) fn create(
name: String,
public_id: Option<String>,
system_id: Option<String>,
arena: &mut Arena<XpathItemTreeNode>,
) -> NodeId {
let node_id = arena.new_node(XpathItemTreeNode::DoctypeNode(DoctypeNode::new(
name, public_id, system_id,
)));
arena
.get_mut(node_id)
.unwrap()
.get_mut()
.as_doctype_node_mut()
.unwrap()
.set_id(node_id);
node_id
}
pub(crate) fn set_id(&mut self, id: NodeId) {
self.id = Some(id);
}
pub(crate) fn id(&self) -> Result<NodeId, ExpressionApplyError> {
self.id.ok_or_else(|| ExpressionApplyError::new("BUG: node ID not set -- was set_id() called?".to_string()))
}
}
impl PartialEq for DoctypeNode {
fn eq(&self, other: &Self) -> bool {
self.name == other.name
&& self.public_id == other.public_id
&& self.system_id == other.system_id
}
}
impl PartialOrd for DoctypeNode {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for DoctypeNode {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
(&self.name, &self.public_id, &self.system_id).cmp(&(
&other.name,
&other.public_id,
&other.system_id,
))
}
}
impl std::hash::Hash for DoctypeNode {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.name.hash(state);
self.public_id.hash(state);
self.system_id.hash(state);
}
}
impl Display for DoctypeNode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match (&self.public_id, &self.system_id) {
(Some(public), Some(system)) => {
write!(
f,
r#"<!DOCTYPE {} PUBLIC "{}" "{}">"#,
self.name, public, system
)
}
(Some(public), None) => {
write!(f, r#"<!DOCTYPE {} PUBLIC "{}">"#, self.name, public)
}
(None, Some(system)) => {
write!(f, r#"<!DOCTYPE {} SYSTEM "{}">"#, self.name, system)
}
(None, None) => {
write!(f, "<!DOCTYPE {}>", self.name)
}
}
}
}
#[derive(Eq, Clone)]
pub struct TextNode {
id: Option<NodeId>,
pub content: String,
}
impl Debug for TextNode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("TextNode")
.field("content", &self.content)
.finish()
}
}
impl TextNode {
pub(crate) fn new(content: String) -> Self {
Self { id: None, content }
}
pub(crate) fn set_id(&mut self, id: NodeId) {
self.id = Some(id);
}
pub(crate) fn id(&self) -> Result<NodeId, ExpressionApplyError> {
self.id.ok_or_else(|| ExpressionApplyError::new("BUG: node ID not set -- was set_id() called?".to_string()))
}
pub fn is_whitespace(&self) -> bool {
self.content.trim().is_empty()
}
pub fn parent<'tree>(&self, tree: &'tree XpathItemTree) -> Option<&'tree XpathItemTreeNode> {
tree.get(self.id().ok()?).parent(tree)
}
pub fn display(
&self,
_tree: &XpathItemTree,
formatting: DisplayFormatting,
indent: usize,
) -> String {
match formatting {
DisplayFormatting::Raw => escape_text_content(&self.content),
DisplayFormatting::NoChildren => {
let indentation = " ".repeat(indent);
format!("{}{}", indentation, self.content)
}
DisplayFormatting::Pretty => {
let indentation = " ".repeat(indent);
format!("{}{}", indentation, self.content.trim())
}
}
}
}
impl PartialEq for TextNode {
fn eq(&self, other: &Self) -> bool {
self.content == other.content
}
}
impl std::hash::Hash for TextNode {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.content.hash(state);
}
}
impl Display for TextNode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.content)
}
}