use std::error::Error;
use std::fmt::{Display, Formatter};
use crate::engine::{ElementNameError, ElementNameKind, ElementNameValue};
use crate::templatemode::TemplateMode;
use crate::util::{Utf16String, case_fold_unit};
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum MatchingElementNameError {
IllegalArgument(&'static str),
ElementName(ElementNameError),
}
impl MatchingElementNameError {
#[must_use]
pub const fn class_name(&self) -> &'static str {
match self {
Self::IllegalArgument(_) => "java.lang.IllegalArgumentException",
Self::ElementName(error) => error.class_name(),
}
}
}
impl Display for MatchingElementNameError {
fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Self::IllegalArgument(message) => formatter.write_str(message),
Self::ElementName(error) => Display::fmt(error, formatter),
}
}
}
impl Error for MatchingElementNameError {}
pub struct MatchingElementName {
template_mode: TemplateMode,
matching_element_name: Option<ElementNameValue>,
matching_all_elements_with_prefix: Option<Utf16String>,
matching_all_elements: bool,
}
impl MatchingElementName {
pub fn for_element_name(
template_mode: Option<TemplateMode>,
matching_element_name: Option<ElementNameValue>,
) -> Result<Self, MatchingElementNameError> {
let template_mode = require_mode(template_mode)?;
let matching_element_name = matching_element_name.ok_or(
MatchingElementNameError::IllegalArgument("Matching element name cannot be null"),
)?;
validate_kind(
template_mode,
matching_element_name.as_element_name().kind(),
)?;
Ok(Self {
template_mode,
matching_element_name: Some(matching_element_name),
matching_all_elements_with_prefix: None,
matching_all_elements: false,
})
}
pub fn for_all_elements_with_prefix(
template_mode: Option<TemplateMode>,
prefix: Option<Utf16String>,
) -> Result<Self, MatchingElementNameError> {
Ok(Self {
template_mode: require_mode(template_mode)?,
matching_element_name: None,
matching_all_elements_with_prefix: prefix,
matching_all_elements: false,
})
}
pub fn for_all_elements(
template_mode: Option<TemplateMode>,
) -> Result<Self, MatchingElementNameError> {
Ok(Self {
template_mode: require_mode(template_mode)?,
matching_element_name: None,
matching_all_elements_with_prefix: None,
matching_all_elements: true,
})
}
#[must_use]
pub const fn get_template_mode(&self) -> TemplateMode {
self.template_mode
}
#[must_use]
pub const fn get_matching_element_name(&self) -> Option<&ElementNameValue> {
self.matching_element_name.as_ref()
}
#[must_use]
pub const fn get_matching_all_elements_with_prefix(&self) -> Option<&Utf16String> {
self.matching_all_elements_with_prefix.as_ref()
}
#[must_use]
pub const fn is_matching_all_elements(&self) -> bool {
self.matching_all_elements
}
pub fn matches(
&self,
element_name: Option<&ElementNameValue>,
) -> Result<bool, MatchingElementNameError> {
let element_name = element_name.ok_or(MatchingElementNameError::IllegalArgument(
"Element name cannot be null",
))?;
if let Some(expected) = self.matching_element_name.as_ref() {
return Ok(expected.as_element_name() == element_name.as_element_name());
}
if !kind_matches_mode(self.template_mode, element_name.as_element_name().kind()) {
return Ok(false);
}
if self.matching_all_elements {
return Ok(true);
}
let actual_prefix = element_name.as_element_name().get_prefix();
let Some(expected_prefix) = self.matching_all_elements_with_prefix.as_ref() else {
return Ok(actual_prefix.is_none());
};
let Some(actual_prefix) = actual_prefix else {
return Ok(false);
};
Ok(text_equals(
self.template_mode.is_case_sensitive(),
expected_prefix,
actual_prefix,
))
}
pub fn to_utf16_string(&self) -> Result<Utf16String, MatchingElementNameError> {
if let Some(name) = self.matching_element_name.as_ref() {
return name
.as_element_name()
.to_utf16_string()
.map_err(MatchingElementNameError::ElementName);
}
if self.matching_all_elements {
return Ok(Utf16String::from_rust_str("*"));
}
let Some(prefix) = self.matching_all_elements_with_prefix.as_ref() else {
return Ok(Utf16String::from_rust_str("[^:]*"));
};
let mut result = prefix.as_utf16().to_vec();
result.extend(":*".encode_utf16());
Ok(Utf16String::from_utf16(result))
}
}
fn require_mode(mode: Option<TemplateMode>) -> Result<TemplateMode, MatchingElementNameError> {
mode.ok_or(MatchingElementNameError::IllegalArgument(
"Template mode cannot be null",
))
}
fn validate_kind(
mode: TemplateMode,
kind: ElementNameKind,
) -> Result<(), MatchingElementNameError> {
if kind_matches_mode(mode, kind) {
return Ok(());
}
let message = match mode {
TemplateMode::HTML => {
"Element names for HTML template mode must be of class org.thymeleaf.engine.HTMLElementName"
}
TemplateMode::XML => {
"Element names for XML template mode must be of class org.thymeleaf.engine.XMLElementName"
}
mode if mode.is_text() => {
"Element names for any text template modes must be of class org.thymeleaf.engine.TextElementName"
}
_ => return Ok(()),
};
Err(MatchingElementNameError::IllegalArgument(message))
}
fn kind_matches_mode(mode: TemplateMode, kind: ElementNameKind) -> bool {
match mode {
TemplateMode::HTML => kind == ElementNameKind::Html,
TemplateMode::XML => kind == ElementNameKind::Xml,
mode if mode.is_text() => kind == ElementNameKind::Text,
_ => true,
}
}
fn text_equals(case_sensitive: bool, left: &Utf16String, right: &Utf16String) -> bool {
left.len() == right.len()
&& left
.as_utf16()
.iter()
.zip(right.as_utf16())
.all(|(left, right)| {
left == right
|| (!case_sensitive && case_fold_unit(*left) == case_fold_unit(*right))
})
}