use std::sync::Arc;
use crate::context::ITemplateContext;
use crate::expression::{IStandardExpression, StandardExpressionResult, StandardExpressions};
use crate::model::{ICDATASection, IComment, IProcessableElementTag, IText};
use crate::util::{CharSequenceValue, TextUtilsError, Utf16String};
use super::AttributeName;
pub struct EngineEventUtils;
impl EngineEventUtils {
pub fn is_whitespace_text(text: Option<&dyn IText>) -> Result<bool, TextUtilsError> {
text.map_or(Ok(false), |text| compute_whitespace(text))
}
pub fn is_whitespace_cdata(
cdata_section: Option<&dyn ICDATASection>,
) -> Result<bool, TextUtilsError> {
let Some(cdata_section) = cdata_section else {
return Ok(false);
};
let content = cdata_section
.get_content()?
.ok_or(TextUtilsError::NullPointer)?;
compute_whitespace(&content)
}
pub fn is_whitespace_comment(comment: Option<&dyn IComment>) -> Result<bool, TextUtilsError> {
let Some(comment) = comment else {
return Ok(false);
};
let content = comment.get_content()?.ok_or(TextUtilsError::NullPointer)?;
compute_whitespace(&content)
}
pub fn is_inlineable_text(text: Option<&dyn IText>) -> Result<bool, TextUtilsError> {
text.map_or(Ok(false), |text| compute_inlineable(text))
}
pub fn is_inlineable_cdata(
cdata_section: Option<&dyn ICDATASection>,
) -> Result<bool, TextUtilsError> {
let Some(cdata_section) = cdata_section else {
return Ok(false);
};
let content = cdata_section
.get_content()?
.ok_or(TextUtilsError::NullPointer)?;
compute_inlineable(&content)
}
pub fn is_inlineable_comment(comment: Option<&dyn IComment>) -> Result<bool, TextUtilsError> {
let Some(comment) = comment else {
return Ok(false);
};
let content = comment.get_content()?.ok_or(TextUtilsError::NullPointer)?;
compute_inlineable(&content)
}
pub fn compute_attribute_expression(
context: &dyn ITemplateContext,
tag: &dyn IProcessableElementTag,
attribute_name: &AttributeName,
attribute_value: &Utf16String,
) -> StandardExpressionResult<Arc<dyn IStandardExpression>> {
let Some(processable_tag) = tag.as_engine_processable_element_tag() else {
return parse_attribute_expression(context, attribute_value);
};
let attribute = processable_tag
.get_attribute_name(attribute_name)
.ok_or_else(|| {
Box::new(crate::exceptions::TemplateProcessingException::new(Some(
"Cannot compute expression for an attribute not present in the tag".to_owned(),
))) as crate::expression::StandardExpressionError
})?;
if let Some(cached) = attribute.get_cached_standard_expression()
&& let Some(expression) = cached.downcast_ref::<Arc<dyn IStandardExpression>>()
{
return Ok(Arc::clone(expression));
}
let expression = parse_attribute_expression(context, attribute_value)?;
if !expression.is_fragment_expression()
&& !attribute_value.as_utf16().contains(&u16::from(b'_'))
{
attribute.set_cached_standard_expression(Some(Arc::new(Arc::clone(&expression))));
}
Ok(expression)
}
}
fn parse_attribute_expression(
context: &dyn ITemplateContext,
attribute_value: &Utf16String,
) -> StandardExpressionResult<Arc<dyn IStandardExpression>> {
StandardExpressions::get_expression_parser(context.get_configuration())?
.parse_expression(context, Some(attribute_value))
}
fn compute_whitespace(text: &dyn CharSequenceValue) -> Result<bool, TextUtilsError> {
let mut remaining = text.length()?;
if remaining == 0 {
return Ok(false);
}
while remaining != 0 {
remaining -= 1;
if !is_whitespace(text.char_at(remaining)?) {
return Ok(false);
}
}
Ok(true)
}
pub(crate) fn compute_inlineable(text: &dyn CharSequenceValue) -> Result<bool, TextUtilsError> {
let mut remaining = text.length()?;
if remaining == 0 {
return Ok(false);
}
let mut previous = 0_u16;
let mut inline = 0_u8;
while remaining != 0 {
remaining -= 1;
let current = text.char_at(remaining)?;
if remaining > 0 && current == u16::from(b']') && previous == u16::from(b']') {
inline = 1;
remaining -= 1;
previous = text.char_at(remaining)?;
} else if remaining > 0 && current == u16::from(b')') && previous == u16::from(b']') {
inline = 2;
remaining -= 1;
previous = text.char_at(remaining)?;
} else if (inline == 1 && current == u16::from(b'[') && previous == u16::from(b'['))
|| (inline == 2 && current == u16::from(b'[') && previous == u16::from(b'('))
{
return Ok(true);
} else {
previous = current;
}
}
Ok(false)
}
fn is_whitespace(character: u16) -> bool {
matches!(
character,
0x0009..=0x000D
| 0x001C..=0x0020
| 0x1680
| 0x2000..=0x2006
| 0x2008..=0x200A
| 0x2028
| 0x2029
| 0x205F
| 0x3000
)
}