thymeleaf 0.1.0-beta.0

A framework-neutral Thymeleaf-compatible dynamic template engine for Rust
Documentation
use std::fmt::{Display, Formatter};
use std::io;
use std::sync::Arc;

use crate::model::{IModelVisitor, IProcessingInstruction, ITemplateEvent};
use crate::util::{TemplateWriter, Utf16String};

use super::{AbstractTemplateEvent, IEngineTemplateEvent, ITemplateHandler};

/// 引擎内部的不可变 XML processing instruction 事件。
///
/// 对应 Java: `org.thymeleaf.engine.ProcessingInstruction`。
pub struct ProcessingInstruction {
    template_event: AbstractTemplateEvent,
    target: Option<Utf16String>,
    content: Option<Utf16String>,
    processing_instruction: Utf16String,
}

impl ProcessingInstruction {
    /// 从 target 与可空内容计算完整 processing instruction。
    ///
    /// 对应 Java: `ProcessingInstruction#ProcessingInstruction(String,String)`。
    #[must_use]
    pub fn new(target: Option<Utf16String>, content: Option<Utf16String>) -> Self {
        let processing_instruction =
            compute_processing_instruction(target.as_ref(), content.as_ref());
        Self {
            template_event: AbstractTemplateEvent::new(),
            target,
            content,
            processing_instruction,
        }
    }

    /// 从 parser 保留的完整文本、分解字段和位置创建事件。
    ///
    /// 对应 Java:
    /// `ProcessingInstruction#ProcessingInstruction(String,String,String,String,int,int)`。
    /// 完整文本为 null 时按 target/content 重新计算。
    #[must_use]
    pub fn with_location(
        processing_instruction: Option<Utf16String>,
        target: Option<Utf16String>,
        content: Option<Utf16String>,
        template_name: Option<Utf16String>,
        line: i32,
        col: i32,
    ) -> Self {
        let processing_instruction = processing_instruction
            .unwrap_or_else(|| compute_processing_instruction(target.as_ref(), content.as_ref()));
        Self {
            template_event: AbstractTemplateEvent::with_location(template_name, line, col),
            target,
            content,
            processing_instruction,
        }
    }
}

impl IProcessingInstruction for ProcessingInstruction {
    fn get_target(&self) -> Option<&Utf16String> {
        self.target.as_ref()
    }

    fn get_content(&self) -> Option<&Utf16String> {
        self.content.as_ref()
    }

    fn get_processing_instruction(&self) -> Option<&Utf16String> {
        Some(&self.processing_instruction)
    }
}

impl ITemplateEvent for ProcessingInstruction {
    fn has_location(&self) -> bool {
        self.template_event.has_location()
    }

    fn get_template_name(&self) -> Option<&Utf16String> {
        self.template_event.get_template_name()
    }

    fn get_line(&self) -> i32 {
        self.template_event.get_line()
    }

    fn get_col(&self) -> i32 {
        self.template_event.get_col()
    }

    fn accept(&self, visitor: &mut dyn IModelVisitor) {
        visitor.visit_processing_instruction(self);
    }

    fn be_handled(
        self: Arc<Self>,
        handler: &mut dyn ITemplateHandler,
    ) -> Result<(), Box<dyn crate::exceptions::TemplateEngineException>> {
        handler.handle_processing_instruction(self)
    }

    fn write(&self, writer: &mut dyn TemplateWriter) -> io::Result<()> {
        writer.write_utf16(self.processing_instruction.as_utf16())
    }
}

impl IEngineTemplateEvent for ProcessingInstruction {}

impl Display for ProcessingInstruction {
    fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
        formatter.write_str(&self.processing_instruction.to_string_lossy())
    }
}

fn compute_processing_instruction(
    target: Option<&Utf16String>,
    content: Option<&Utf16String>,
) -> Utf16String {
    let mut result = Vec::with_capacity(100);
    result.extend("<?".encode_utf16());
    append_nullable(&mut result, target);
    if let Some(content) = content {
        result.push(u16::from(b' '));
        result.extend_from_slice(content.as_utf16());
    }
    result.extend("?>".encode_utf16());
    Utf16String::from_utf16(result)
}

fn append_nullable(result: &mut Vec<u16>, value: Option<&Utf16String>) {
    match value {
        Some(value) => result.extend_from_slice(value.as_utf16()),
        None => result.extend("null".encode_utf16()),
    }
}