use std::collections::HashMap;
use std::error::Error;
use std::fmt::{Display, Formatter};
use std::sync::{Arc, RwLock};
use crate::element::{ElementProcessorSet, IElementProcessor};
use crate::templatemode::TemplateMode;
use crate::util::Utf16String;
use super::{
AttributeDefinition, AttributeDefinitionError, AttributeNameValue, AttributeNames,
AttributeNamesError, HTMLAttributeDefinition, TextAttributeDefinition, XMLAttributeDefinition,
};
pub type ElementProcessorsByTemplateMode = HashMap<TemplateMode, Vec<Arc<dyn IElementProcessor>>>;
type AttributeDefinitionRepository = RwLock<HashMap<Utf16String, AttributeDefinitionValue>>;
#[derive(Clone)]
pub enum AttributeDefinitionValue {
Html(Arc<HTMLAttributeDefinition>),
Xml(Arc<XMLAttributeDefinition>),
Text(Arc<TextAttributeDefinition>),
}
impl AttributeDefinitionValue {
#[must_use]
pub fn as_attribute_definition(&self) -> &AttributeDefinition {
match self {
Self::Html(value) => value.as_attribute_definition(),
Self::Xml(value) => value.as_attribute_definition(),
Self::Text(value) => value.as_attribute_definition(),
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum AttributeDefinitionsError {
IllegalArgument(String),
Configuration(String),
AttributeNames(AttributeNamesError),
AttributeDefinition(AttributeDefinitionError),
}
impl AttributeDefinitionsError {
#[must_use]
pub fn class_name(&self) -> &str {
match self {
Self::IllegalArgument(_) => "java.lang.IllegalArgumentException",
Self::Configuration(_) => "org.thymeleaf.exceptions.ConfigurationException",
Self::AttributeNames(error) => error.class_name(),
Self::AttributeDefinition(error) => error.class_name(),
}
}
}
impl Display for AttributeDefinitionsError {
fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Self::IllegalArgument(message) | Self::Configuration(message) => {
formatter.write_str(message)
}
Self::AttributeNames(error) => Display::fmt(error, formatter),
Self::AttributeDefinition(error) => Display::fmt(error, formatter),
}
}
}
impl Error for AttributeDefinitionsError {}
impl From<AttributeNamesError> for AttributeDefinitionsError {
fn from(value: AttributeNamesError) -> Self {
Self::AttributeNames(value)
}
}
impl From<AttributeDefinitionError> for AttributeDefinitionsError {
fn from(value: AttributeDefinitionError) -> Self {
Self::AttributeDefinition(value)
}
}
pub struct AttributeDefinitions {
processors: Arc<ElementProcessorsByTemplateMode>,
html_repository: AttributeDefinitionRepository,
xml_repository: AttributeDefinitionRepository,
text_repository: AttributeDefinitionRepository,
javascript_repository: AttributeDefinitionRepository,
css_repository: AttributeDefinitionRepository,
}
impl AttributeDefinitions {
pub fn new(
processors: ElementProcessorsByTemplateMode,
) -> Result<Self, AttributeDefinitionsError> {
let manager = Self {
processors: Arc::new(processors),
html_repository: RwLock::new(HashMap::new()),
xml_repository: RwLock::new(HashMap::new()),
text_repository: RwLock::new(HashMap::new()),
javascript_repository: RwLock::new(HashMap::new()),
css_repository: RwLock::new(HashMap::new()),
};
for name in STANDARD_HTML_ATTRIBUTE_NAMES {
manager.for_html_name(Some(&Utf16String::from_rust_str(name)))?;
}
Ok(manager)
}
#[must_use]
pub fn all_standard_html_attribute_names() -> Vec<&'static str> {
let mut values = STANDARD_HTML_ATTRIBUTE_NAMES.to_vec();
values.sort_unstable();
values
}
pub fn for_name(
&self,
template_mode: Option<TemplateMode>,
attribute_name: Option<&Utf16String>,
) -> Result<AttributeDefinitionValue, AttributeDefinitionsError> {
let mode = require_mode(template_mode)?;
match mode {
TemplateMode::HTML => self
.for_html_name(attribute_name)
.map(AttributeDefinitionValue::Html),
TemplateMode::XML => self
.for_xml_name(attribute_name)
.map(AttributeDefinitionValue::Xml),
TemplateMode::TEXT | TemplateMode::JAVASCRIPT | TemplateMode::CSS => self
.for_text_mode_name(mode, attribute_name)
.map(AttributeDefinitionValue::Text),
TemplateMode::RAW => Err(raw_mode_error(mode)),
}
}
pub fn for_name_with_prefix(
&self,
template_mode: Option<TemplateMode>,
prefix: Option<&Utf16String>,
attribute_name: Option<&Utf16String>,
) -> Result<AttributeDefinitionValue, AttributeDefinitionsError> {
let mode = require_mode(template_mode)?;
match mode {
TemplateMode::HTML => self
.for_html_name_with_prefix(prefix, attribute_name)
.map(AttributeDefinitionValue::Html),
TemplateMode::XML => self
.for_xml_name_with_prefix(prefix, attribute_name)
.map(AttributeDefinitionValue::Xml),
TemplateMode::TEXT | TemplateMode::JAVASCRIPT | TemplateMode::CSS => self
.for_text_mode_name_with_prefix(mode, prefix, attribute_name)
.map(AttributeDefinitionValue::Text),
TemplateMode::RAW => Err(raw_mode_error(mode)),
}
}
pub fn for_name_buffer(
&self,
template_mode: Option<TemplateMode>,
attribute_name: Option<&[u16]>,
attribute_name_offset: i32,
attribute_name_len: i32,
) -> Result<AttributeDefinitionValue, AttributeDefinitionsError> {
let mode = require_mode(template_mode)?;
if mode == TemplateMode::RAW {
return Err(raw_mode_error(mode));
}
let name = AttributeNames::for_name_buffer(
Some(mode),
attribute_name,
attribute_name_offset,
attribute_name_len,
)?;
self.get_or_build(mode, name)
}
pub fn for_html_name(
&self,
attribute_name: Option<&Utf16String>,
) -> Result<Arc<HTMLAttributeDefinition>, AttributeDefinitionsError> {
let name = AttributeNames::for_html_name(attribute_name)?;
self.get_or_build(TemplateMode::HTML, AttributeNameValue::Html(name))?
.into_html()
}
pub fn for_html_name_with_prefix(
&self,
prefix: Option<&Utf16String>,
attribute_name: Option<&Utf16String>,
) -> Result<Arc<HTMLAttributeDefinition>, AttributeDefinitionsError> {
let name = AttributeNames::for_html_name_with_prefix(prefix, attribute_name)?;
self.get_or_build(TemplateMode::HTML, AttributeNameValue::Html(name))?
.into_html()
}
pub fn for_xml_name(
&self,
attribute_name: Option<&Utf16String>,
) -> Result<Arc<XMLAttributeDefinition>, AttributeDefinitionsError> {
let name = AttributeNames::for_xml_name(attribute_name)?;
self.get_or_build(TemplateMode::XML, AttributeNameValue::Xml(name))?
.into_xml()
}
pub fn for_xml_name_with_prefix(
&self,
prefix: Option<&Utf16String>,
attribute_name: Option<&Utf16String>,
) -> Result<Arc<XMLAttributeDefinition>, AttributeDefinitionsError> {
let name = AttributeNames::for_xml_name_with_prefix(prefix, attribute_name)?;
self.get_or_build(TemplateMode::XML, AttributeNameValue::Xml(name))?
.into_xml()
}
pub fn for_text_name(
&self,
attribute_name: Option<&Utf16String>,
) -> Result<Arc<TextAttributeDefinition>, AttributeDefinitionsError> {
self.for_text_mode_name(TemplateMode::TEXT, attribute_name)
}
pub fn for_javascript_name(
&self,
attribute_name: Option<&Utf16String>,
) -> Result<Arc<TextAttributeDefinition>, AttributeDefinitionsError> {
self.for_text_mode_name(TemplateMode::JAVASCRIPT, attribute_name)
}
pub fn for_css_name(
&self,
attribute_name: Option<&Utf16String>,
) -> Result<Arc<TextAttributeDefinition>, AttributeDefinitionsError> {
self.for_text_mode_name(TemplateMode::CSS, attribute_name)
}
fn for_text_mode_name(
&self,
mode: TemplateMode,
attribute_name: Option<&Utf16String>,
) -> Result<Arc<TextAttributeDefinition>, AttributeDefinitionsError> {
let name = AttributeNames::for_text_name(attribute_name)?;
self.get_or_build(mode, AttributeNameValue::Text(name))?
.into_text()
}
fn for_text_mode_name_with_prefix(
&self,
mode: TemplateMode,
prefix: Option<&Utf16String>,
attribute_name: Option<&Utf16String>,
) -> Result<Arc<TextAttributeDefinition>, AttributeDefinitionsError> {
let name = AttributeNames::for_text_name_with_prefix(prefix, attribute_name)?;
self.get_or_build(mode, AttributeNameValue::Text(name))?
.into_text()
}
fn get_or_build(
&self,
mode: TemplateMode,
name: AttributeNameValue,
) -> Result<AttributeDefinitionValue, AttributeDefinitionsError> {
let name_arc = name.as_attribute_name().get_complete_attribute_names();
let complete_names = read_lock(&name_arc);
let key = complete_names.first().and_then(Clone::clone).ok_or(
AttributeDefinitionError::AttributeName(
super::AttributeNameError::EmptyCompleteAttributeNames,
),
)?;
let repository = self.repository(mode);
if let Some(value) = read_lock(repository).get(&key) {
return Ok(value.clone());
}
let mut repository = write_lock(repository);
if let Some(value) = repository.get(&key) {
return Ok(value.clone());
}
let value = self.build(mode, name)?;
for alias in complete_attribute_names(value.as_attribute_definition())? {
repository.insert(alias, value.clone());
}
Ok(value)
}
fn build(
&self,
mode: TemplateMode,
name: AttributeNameValue,
) -> Result<AttributeDefinitionValue, AttributeDefinitionsError> {
let mut associated = ElementProcessorSet::new();
for processor in self.processors.get(&mode).into_iter().flatten() {
if processor.get_template_mode() != Some(mode) {
continue;
}
let element_match = processor.get_matching_element_name();
let attribute_match = processor.get_matching_attribute_name();
if element_match.is_some_and(|value| value.get_template_mode() != mode)
|| attribute_match.is_some_and(|value| value.get_template_mode() != mode)
{
return Err(AttributeDefinitionsError::Configuration(format!(
"{mode} processors must return {mode} element names and {mode} attribute names (processor: {})",
processor.class_name()
)));
}
let Some(attribute_match) = attribute_match else {
continue;
};
if attribute_match.is_matching_all_attributes() {
continue;
}
if !attribute_match
.matches(Some(&name))
.map_err(|error| AttributeDefinitionsError::Configuration(error.to_string()))?
{
continue;
}
associated.insert(Some(Arc::clone(processor)));
}
let associated = Arc::new(RwLock::new(associated));
match name {
AttributeNameValue::Html(name) => {
let boolean_attribute =
complete_name_values(name.as_attribute_name())
.iter()
.any(|value| {
BOOLEAN_HTML_ATTRIBUTE_NAMES.contains(&value.to_string_lossy().as_str())
});
Ok(AttributeDefinitionValue::Html(Arc::new(
HTMLAttributeDefinition::new(name, boolean_attribute, associated)?,
)))
}
AttributeNameValue::Xml(name) => Ok(AttributeDefinitionValue::Xml(Arc::new(
XMLAttributeDefinition::new(name, associated)?,
))),
AttributeNameValue::Text(name) => Ok(AttributeDefinitionValue::Text(Arc::new(
TextAttributeDefinition::new(name, associated)?,
))),
}
}
fn repository(
&self,
mode: TemplateMode,
) -> &RwLock<HashMap<Utf16String, AttributeDefinitionValue>> {
match mode {
TemplateMode::HTML => &self.html_repository,
TemplateMode::XML => &self.xml_repository,
TemplateMode::TEXT => &self.text_repository,
TemplateMode::JAVASCRIPT => &self.javascript_repository,
TemplateMode::CSS => &self.css_repository,
TemplateMode::RAW => unreachable!("RAW has no attribute repository"),
}
}
}
impl AttributeDefinitionValue {
fn into_html(self) -> Result<Arc<HTMLAttributeDefinition>, AttributeDefinitionsError> {
match self {
Self::Html(value) => Ok(value),
_ => Err(AttributeDefinitionsError::Configuration(
"HTML repository returned a non-HTML definition".into(),
)),
}
}
fn into_xml(self) -> Result<Arc<XMLAttributeDefinition>, AttributeDefinitionsError> {
match self {
Self::Xml(value) => Ok(value),
_ => Err(AttributeDefinitionsError::Configuration(
"XML repository returned a non-XML definition".into(),
)),
}
}
fn into_text(self) -> Result<Arc<TextAttributeDefinition>, AttributeDefinitionsError> {
match self {
Self::Text(value) => Ok(value),
_ => Err(AttributeDefinitionsError::Configuration(
"text repository returned a non-text definition".into(),
)),
}
}
}
fn complete_name_values(name: &super::AttributeName) -> Vec<Utf16String> {
let values = name.get_complete_attribute_names();
read_lock(&values).iter().filter_map(Clone::clone).collect()
}
fn complete_attribute_names(
definition: &AttributeDefinition,
) -> Result<Vec<Utf16String>, AttributeDefinitionsError> {
let values = complete_name_values(definition.get_attribute_name().as_attribute_name());
if values.is_empty() {
return Err(AttributeDefinitionError::AttributeName(
super::AttributeNameError::EmptyCompleteAttributeNames,
)
.into());
}
Ok(values)
}
fn require_mode(mode: Option<TemplateMode>) -> Result<TemplateMode, AttributeDefinitionsError> {
mode.ok_or_else(|| {
AttributeDefinitionsError::IllegalArgument("Template Mode cannot be null".into())
})
}
fn raw_mode_error(mode: TemplateMode) -> AttributeDefinitionsError {
AttributeDefinitionsError::IllegalArgument(format!(
"Attribute Definitions cannot be obtained for {mode} template mode "
))
}
fn read_lock<T>(lock: &RwLock<T>) -> std::sync::RwLockReadGuard<'_, T> {
lock.read()
.unwrap_or_else(std::sync::PoisonError::into_inner)
}
fn write_lock<T>(lock: &RwLock<T>) -> std::sync::RwLockWriteGuard<'_, T> {
lock.write()
.unwrap_or_else(std::sync::PoisonError::into_inner)
}
const BOOLEAN_HTML_ATTRIBUTE_NAMES: &[&str] = &[
"async",
"autofocus",
"autoplay",
"checked",
"controls",
"declare",
"default",
"defer",
"disabled",
"formnovalidate",
"hidden",
"ismap",
"loop",
"multiple",
"novalidate",
"nowrap",
"open",
"pubdate",
"readonly",
"required",
"reversed",
"selected",
"scoped",
"seamless",
];
const STANDARD_HTML_ATTRIBUTE_NAMES: &[&str] = &[
"abbr",
"accept",
"accept-charset",
"accesskey",
"action",
"align",
"alt",
"archive",
"async",
"autocomplete",
"autofocus",
"autoplay",
"axis",
"border",
"cellpadding",
"cellspacing",
"challenge",
"char",
"charoff",
"charset",
"checked",
"cite",
"class",
"classid",
"codebase",
"codetype",
"cols",
"colspan",
"command",
"content",
"contenteditable",
"contextmenu",
"controls",
"coords",
"data",
"datetime",
"declare",
"default",
"defer",
"dir",
"disabled",
"draggable",
"dropzone",
"enctype",
"for",
"form",
"formaction",
"formenctype",
"formmethod",
"formnovalidate",
"formtarget",
"frame",
"headers",
"height",
"hidden",
"high",
"href",
"hreflang",
"http-equiv",
"icon",
"id",
"ismap",
"keytype",
"kind",
"label",
"lang",
"list",
"longdesc",
"loop",
"low",
"max",
"maxlength",
"media",
"method",
"min",
"multiple",
"muted",
"name",
"nohref",
"novalidate",
"nowrap",
"onabort",
"onafterprint",
"onbeforeprint",
"onbeforeunload",
"onblur",
"oncanplay",
"oncanplaythrough",
"onchange",
"onclick",
"oncontextmenu",
"oncuechange",
"ondblclick",
"ondrag",
"ondragend",
"ondragenter",
"ondragleave",
"ondragover",
"ondragstart",
"ondrop",
"ondurationchange",
"onemptied",
"onended",
"onerror",
"onfocus",
"onformchange",
"onforminput",
"onhaschange",
"oninput",
"oninvalid",
"onkeydown",
"onkeypress",
"onkeyup",
"onload",
"onloadeddata",
"onloadedmetadata",
"onloadstart",
"onmessage",
"onmousedown",
"onmousemove",
"onmouseout",
"onmouseover",
"onmouseup",
"onmousewheel",
"onoffline",
"ononline",
"onpagehide",
"onpageshow",
"onpause",
"onplay",
"onplaying",
"onpopstate",
"onprogress",
"onratechange",
"onredo",
"onreset",
"onresize",
"onscroll",
"onseeked",
"onseeking",
"onselect",
"onstalled",
"onstorage",
"onsubmit",
"onsuspend",
"ontimeupdate",
"onundo",
"onunload",
"onvolumechange",
"onwaiting",
"open",
"optimum",
"pattern",
"placeholder",
"poster",
"preload",
"profile",
"pubdate",
"radiogroup",
"readonly",
"rel",
"required",
"rev",
"reversed",
"rows",
"rowspan",
"rules",
"scheme",
"scope",
"scoped",
"seamless",
"selected",
"shape",
"size",
"span",
"spellcheck",
"src",
"srclang",
"standby",
"style",
"summary",
"tabindex",
"title",
"translate",
"type",
"usemap",
"valign",
"value",
"valuetype",
"width",
"xml:lang",
"xml:space",
"xmlns",
];