use std::error::Error;
use std::fmt::{Display, Formatter};
use crate::engine::{AttributeNameError, AttributeNameKind, AttributeNameValue};
use crate::templatemode::TemplateMode;
use crate::util::{Utf16String, case_fold_unit};
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum MatchingAttributeNameError {
IllegalArgument(&'static str),
AttributeName(AttributeNameError),
}
impl MatchingAttributeNameError {
#[must_use]
pub const fn class_name(&self) -> &'static str {
match self {
Self::IllegalArgument(_) => "java.lang.IllegalArgumentException",
Self::AttributeName(error) => error.class_name(),
}
}
}
impl Display for MatchingAttributeNameError {
fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Self::IllegalArgument(message) => formatter.write_str(message),
Self::AttributeName(error) => Display::fmt(error, formatter),
}
}
}
impl Error for MatchingAttributeNameError {}
pub struct MatchingAttributeName {
template_mode: TemplateMode,
matching_attribute_name: Option<AttributeNameValue>,
matching_all_attributes_with_prefix: Option<Utf16String>,
matching_all_attributes: bool,
}
impl MatchingAttributeName {
pub fn for_attribute_name(
template_mode: Option<TemplateMode>,
matching_attribute_name: Option<AttributeNameValue>,
) -> Result<Self, MatchingAttributeNameError> {
let template_mode = require_mode(template_mode)?;
let matching_attribute_name = matching_attribute_name.ok_or(
MatchingAttributeNameError::IllegalArgument("Matching attribute name cannot be null"),
)?;
validate_kind(
template_mode,
matching_attribute_name.as_attribute_name().kind(),
)?;
Ok(Self {
template_mode,
matching_attribute_name: Some(matching_attribute_name),
matching_all_attributes_with_prefix: None,
matching_all_attributes: false,
})
}
pub fn for_all_attributes_with_prefix(
template_mode: Option<TemplateMode>,
prefix: Option<Utf16String>,
) -> Result<Self, MatchingAttributeNameError> {
Ok(Self {
template_mode: require_mode(template_mode)?,
matching_attribute_name: None,
matching_all_attributes_with_prefix: prefix,
matching_all_attributes: false,
})
}
pub fn for_all_attributes(
template_mode: Option<TemplateMode>,
) -> Result<Self, MatchingAttributeNameError> {
Ok(Self {
template_mode: require_mode(template_mode)?,
matching_attribute_name: None,
matching_all_attributes_with_prefix: None,
matching_all_attributes: true,
})
}
#[must_use]
pub const fn get_template_mode(&self) -> TemplateMode {
self.template_mode
}
#[must_use]
pub const fn get_matching_attribute_name(&self) -> Option<&AttributeNameValue> {
self.matching_attribute_name.as_ref()
}
#[must_use]
pub const fn get_matching_all_attributes_with_prefix(&self) -> Option<&Utf16String> {
self.matching_all_attributes_with_prefix.as_ref()
}
#[must_use]
pub const fn is_matching_all_attributes(&self) -> bool {
self.matching_all_attributes
}
pub fn matches(
&self,
attribute_name: Option<&AttributeNameValue>,
) -> Result<bool, MatchingAttributeNameError> {
let attribute_name = attribute_name.ok_or(MatchingAttributeNameError::IllegalArgument(
"Attributes name cannot be null",
))?;
if let Some(expected) = self.matching_attribute_name.as_ref() {
return expected
.as_attribute_name()
.equals_java(attribute_name.as_attribute_name())
.map_err(MatchingAttributeNameError::AttributeName);
}
if !kind_matches_mode(
self.template_mode,
attribute_name.as_attribute_name().kind(),
) {
return Ok(false);
}
if self.matching_all_attributes {
return Ok(true);
}
let actual_prefix = attribute_name.as_attribute_name().get_prefix();
let Some(expected_prefix) = self.matching_all_attributes_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, MatchingAttributeNameError> {
if let Some(name) = self.matching_attribute_name.as_ref() {
return name
.as_attribute_name()
.to_utf16_string()
.map_err(MatchingAttributeNameError::AttributeName);
}
if self.matching_all_attributes {
return Ok(Utf16String::from_rust_str("*"));
}
let Some(prefix) = self.matching_all_attributes_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, MatchingAttributeNameError> {
mode.ok_or(MatchingAttributeNameError::IllegalArgument(
"Template mode cannot be null",
))
}
fn validate_kind(
mode: TemplateMode,
kind: AttributeNameKind,
) -> Result<(), MatchingAttributeNameError> {
if kind_matches_mode(mode, kind) {
return Ok(());
}
let message = match mode {
TemplateMode::HTML => {
"Attribute names for HTML template mode must be of class org.thymeleaf.engine.HTMLAttributeName"
}
TemplateMode::XML => {
"Attribute names for XML template mode must be of class org.thymeleaf.engine.XMLAttributeName"
}
mode if mode.is_text() => {
"Attribute names for any text template modes must be of class org.thymeleaf.engine.TextAttributeName"
}
_ => return Ok(()),
};
Err(MatchingAttributeNameError::IllegalArgument(message))
}
fn kind_matches_mode(mode: TemplateMode, kind: AttributeNameKind) -> bool {
match mode {
TemplateMode::HTML => kind == AttributeNameKind::Html,
TemplateMode::XML => kind == AttributeNameKind::Xml,
mode if mode.is_text() => kind == AttributeNameKind::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))
})
}