use std::sync::Arc;
use crate::TemplateMode;
use crate::context::ITemplateContext;
use crate::engine::{AttributeName, AttributeNameValue, AttributeNames, ElementNames};
use crate::exceptions::{TemplateEngineException, TemplateProcessingException};
use crate::model::{IAttribute, IModel, IProcessableElementTag, ITemplateEvent};
use crate::processor::{AbstractProcessor, IProcessor};
use crate::util::{EscapedAttributeUtils, Utf16String};
use super::{
IElementModelProcessor, IElementModelStructureHandler, IElementProcessor,
MatchingAttributeName, MatchingElementName,
};
pub struct AbstractAttributeModelProcessor<F> {
processor: AbstractProcessor,
processor_class_name: &'static str,
dialect_prefix: Option<Utf16String>,
matching_element_name: Option<MatchingElementName>,
matching_attribute_name: MatchingAttributeName,
attribute_name: AttributeNameValue,
remove_attribute: bool,
do_process: F,
}
impl<F> AbstractAttributeModelProcessor<F> {
#[allow(clippy::too_many_arguments)]
pub fn new(
template_mode: Option<TemplateMode>,
dialect_prefix: Option<Utf16String>,
element_name: Option<Utf16String>,
prefix_element_name: bool,
attribute_name: Option<Utf16String>,
prefix_attribute_name: bool,
precedence: i32,
remove_attribute: bool,
processor_class_name: &'static str,
do_process: F,
) -> Result<Self, TemplateProcessingException> {
let processor =
AbstractProcessor::new(template_mode, precedence).map_err(configuration_error)?;
let mode = processor.get_template_mode();
let matching_element_name = build_matching_element(
mode,
dialect_prefix.as_ref(),
element_name.as_ref(),
prefix_element_name,
)?;
let normalized = if prefix_attribute_name {
AttributeNames::for_name_with_prefix(
Some(mode),
dialect_prefix.as_ref(),
attribute_name.as_ref(),
)
} else {
AttributeNames::for_name(Some(mode), attribute_name.as_ref())
}
.map_err(configuration_error)?;
let matching_attribute_name =
MatchingAttributeName::for_attribute_name(Some(mode), Some(normalized.clone()))
.map_err(configuration_error)?;
Ok(Self {
processor,
processor_class_name,
dialect_prefix,
matching_element_name,
matching_attribute_name,
attribute_name: normalized,
remove_attribute,
do_process,
})
}
pub fn get_dialect_prefix(&self) -> Option<&Utf16String> {
self.dialect_prefix.as_ref()
}
}
impl<F> IProcessor for AbstractAttributeModelProcessor<F>
where
F: Send + Sync,
{
fn class_name(&self) -> &'static str {
self.processor_class_name
}
fn get_template_mode(&self) -> Option<TemplateMode> {
Some(self.processor.get_template_mode())
}
fn get_precedence(&self) -> i32 {
self.processor.get_precedence()
}
}
impl<F> IElementProcessor for AbstractAttributeModelProcessor<F>
where
F: Fn(
&dyn ITemplateContext,
&mut dyn IModel,
&AttributeName,
Option<Utf16String>,
&mut dyn IElementModelStructureHandler,
) -> Result<(), Box<dyn TemplateEngineException>>
+ Send
+ Sync,
{
fn as_element_model_processor(&self) -> Option<&dyn IElementModelProcessor> {
Some(self)
}
fn get_matching_element_name(&self) -> Option<&MatchingElementName> {
self.matching_element_name.as_ref()
}
fn get_matching_attribute_name(&self) -> Option<&MatchingAttributeName> {
Some(&self.matching_attribute_name)
}
}
impl<F> IElementModelProcessor for AbstractAttributeModelProcessor<F>
where
F: Fn(
&dyn ITemplateContext,
&mut dyn IModel,
&AttributeName,
Option<Utf16String>,
&mut dyn IElementModelStructureHandler,
) -> Result<(), Box<dyn TemplateEngineException>>
+ Send
+ Sync,
{
fn process(
&self,
context: &dyn ITemplateContext,
model: &mut dyn IModel,
structure_handler: &mut dyn IElementModelStructureHandler,
) -> Result<(), Box<dyn TemplateEngineException>> {
let first_event = (model.size() > 0).then(|| model.get(0));
let first_tag = first_event
.as_ref()
.and_then(|event| event.clone().into_processable_element_tag());
let attribute_name = self.attribute_name.as_attribute_name();
let mut operation = || -> Result<(), Box<dyn TemplateEngineException>> {
let first_tag = first_tag.as_ref().ok_or_else(|| {
Box::new(TemplateProcessingException::new(Some(
"Model first event is not an IProcessableElementTag".to_owned(),
))) as Box<dyn TemplateEngineException>
})?;
let attribute_value = EscapedAttributeUtils::unescape_attribute(
Some(context.get_template_mode()),
first_tag.get_attribute_value_by_name(attribute_name),
)
.map_err(|error| Box::new(error) as Box<dyn TemplateEngineException>)?;
(self.do_process)(
context,
model,
attribute_name,
attribute_value,
structure_handler,
)?;
if self.remove_attribute {
let location = locate_first_event_in_model(model, first_event.as_ref());
if let Some(location) = location {
let current_event = model.get(location);
let current_tag = current_event
.clone()
.into_processable_element_tag()
.ok_or_else(|| {
Box::new(TemplateProcessingException::new(Some(
"Located model event is not an IProcessableElementTag".to_owned(),
))) as Box<dyn TemplateEngineException>
})?;
let new_first_event = context
.get_model_factory()
.remove_attribute(current_tag.clone(), attribute_name)
.map_err(|error| Box::new(error) as Box<dyn TemplateEngineException>)?;
if !Arc::ptr_eq(¤t_tag, &new_first_event) {
let replacement: Arc<dyn ITemplateEvent> = new_first_event;
model
.replace(location, Some(replacement))
.map_err(|error| {
Box::new(TemplateProcessingException::with_cause(
Some(error.to_string()),
error,
))
as Box<dyn TemplateEngineException>
})?;
}
}
}
Ok(())
};
match operation() {
Ok(()) => Ok(()),
Err(mut error) => {
if let Some(processing) = error.as_processing_exception_mut() {
enrich_model_attribute_location(
processing,
first_tag.as_deref(),
attribute_name,
);
return Err(error);
}
let (template_name, line, col) =
model_attribute_location(first_tag.as_deref(), attribute_name);
Err(Box::new(
TemplateProcessingException::with_location_and_cause(
Some(format!(
"Error during execution of processor '{}'",
self.processor_class_name
)),
template_name,
line,
col,
ProcessorCause(error),
),
))
}
}
}
}
fn locate_first_event_in_model(
model: &dyn IModel,
first_event: Option<&Arc<dyn ITemplateEvent>>,
) -> Option<usize> {
if let Some(first_event) = first_event {
for index in 0..model.size() {
if Arc::ptr_eq(first_event, &model.get(index)) {
return Some(index);
}
}
}
if model.size() > 0 && model.get(0).into_processable_element_tag().is_some() {
return Some(0);
}
None
}
fn build_matching_element(
mode: TemplateMode,
dialect_prefix: Option<&Utf16String>,
element_name: Option<&Utf16String>,
prefix_element_name: bool,
) -> Result<Option<MatchingElementName>, TemplateProcessingException> {
element_name
.map(|name| {
let normalized = if prefix_element_name {
ElementNames::for_name_with_prefix(Some(mode), dialect_prefix, Some(name))
} else {
ElementNames::for_name(Some(mode), Some(name))
}
.map_err(configuration_error)?;
MatchingElementName::for_element_name(Some(mode), Some(normalized))
.map_err(configuration_error)
})
.transpose()
}
fn enrich_model_attribute_location(
error: &mut TemplateProcessingException,
first_tag: Option<&dyn IProcessableElementTag>,
attribute_name: &AttributeName,
) {
let (template_name, line, col) = model_attribute_location(first_tag, attribute_name);
if !error.has_template_name() {
error.set_template_name(template_name);
}
if !error.has_line_and_col() && line != -1 && col != -1 {
error.set_line_and_col(line, col);
}
}
fn model_attribute_location(
first_tag: Option<&dyn IProcessableElementTag>,
attribute_name: &AttributeName,
) -> (Option<String>, i32, i32) {
let Some(first_tag) = first_tag else {
return (None, -1, -1);
};
let attribute = first_tag.get_attribute_by_name(attribute_name);
(
first_tag
.get_template_name()
.map(Utf16String::to_string_lossy),
attribute.map_or(-1, IAttribute::get_line),
attribute.map_or(-1, IAttribute::get_col),
)
}
fn configuration_error(
error: impl std::error::Error + Send + Sync + 'static,
) -> TemplateProcessingException {
TemplateProcessingException::with_cause(Some(error.to_string()), error)
}
struct ProcessorCause(Box<dyn TemplateEngineException>);
impl std::fmt::Display for ProcessorCause {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(&self.0, formatter)
}
}
impl std::fmt::Debug for ProcessorCause {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Debug::fmt(&self.0, formatter)
}
}
impl std::error::Error for ProcessorCause {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
Some(self.0.as_ref())
}
}