use std::fmt::{Display, Formatter};
use std::io;
use std::sync::Arc;
use crate::model::{IModelVisitor, ITemplateEvent, IXMLDeclaration};
use crate::util::{TemplateWriter, Utf16String};
use super::{AbstractTemplateEvent, IEngineTemplateEvent, ITemplateHandler};
const DEFAULT_KEYWORD: &str = "xml";
const DEFAULT_VERSION: &str = "1.0";
const ATTRIBUTE_NAME_VERSION: &str = "version";
const ATTRIBUTE_NAME_ENCODING: &str = "encoding";
const ATTRIBUTE_NAME_STANDALONE: &str = "standalone";
pub struct XMLDeclaration {
template_event: AbstractTemplateEvent,
keyword: Option<Utf16String>,
version: Option<Utf16String>,
encoding: Option<Utf16String>,
standalone: Option<Utf16String>,
xml_declaration: Utf16String,
}
impl XMLDeclaration {
#[must_use]
pub fn new(encoding: Option<Utf16String>) -> Self {
Self::with_components(
Some(Utf16String::from_rust_str(DEFAULT_KEYWORD)),
Some(Utf16String::from_rust_str(DEFAULT_VERSION)),
encoding,
None,
)
}
#[must_use]
pub fn with_components(
keyword: Option<Utf16String>,
version: Option<Utf16String>,
encoding: Option<Utf16String>,
standalone: Option<Utf16String>,
) -> Self {
let xml_declaration = compute_xml_declaration(
keyword.as_ref(),
version.as_ref(),
encoding.as_ref(),
standalone.as_ref(),
);
Self {
template_event: AbstractTemplateEvent::new(),
keyword,
version,
encoding,
standalone,
xml_declaration,
}
}
#[allow(clippy::too_many_arguments)]
#[must_use]
pub fn with_location(
xml_declaration: Option<Utf16String>,
keyword: Option<Utf16String>,
version: Option<Utf16String>,
encoding: Option<Utf16String>,
standalone: Option<Utf16String>,
template_name: Option<Utf16String>,
line: i32,
col: i32,
) -> Self {
let xml_declaration = xml_declaration.unwrap_or_else(|| {
compute_xml_declaration(
keyword.as_ref(),
version.as_ref(),
encoding.as_ref(),
standalone.as_ref(),
)
});
Self {
template_event: AbstractTemplateEvent::with_location(template_name, line, col),
keyword,
version,
encoding,
standalone,
xml_declaration,
}
}
}
impl IXMLDeclaration for XMLDeclaration {
fn get_keyword(&self) -> Option<&Utf16String> {
self.keyword.as_ref()
}
fn get_version(&self) -> Option<&Utf16String> {
self.version.as_ref()
}
fn get_encoding(&self) -> Option<&Utf16String> {
self.encoding.as_ref()
}
fn get_standalone(&self) -> Option<&Utf16String> {
self.standalone.as_ref()
}
fn get_xml_declaration(&self) -> Option<&Utf16String> {
Some(&self.xml_declaration)
}
}
impl ITemplateEvent for XMLDeclaration {
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_xml_declaration(self);
}
fn be_handled(
self: Arc<Self>,
handler: &mut dyn ITemplateHandler,
) -> Result<(), Box<dyn crate::exceptions::TemplateEngineException>> {
handler.handle_xml_declaration(self)
}
fn write(&self, writer: &mut dyn TemplateWriter) -> io::Result<()> {
writer.write_utf16(self.xml_declaration.as_utf16())
}
}
impl IEngineTemplateEvent for XMLDeclaration {}
impl Display for XMLDeclaration {
fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
formatter.write_str(&self.xml_declaration.to_string_lossy())
}
}
fn compute_xml_declaration(
keyword: Option<&Utf16String>,
version: Option<&Utf16String>,
encoding: Option<&Utf16String>,
standalone: Option<&Utf16String>,
) -> Utf16String {
let mut result = Vec::with_capacity(40);
result.extend("<?".encode_utf16());
append_nullable(&mut result, keyword);
append_attribute(&mut result, ATTRIBUTE_NAME_VERSION, version);
append_attribute(&mut result, ATTRIBUTE_NAME_ENCODING, encoding);
append_attribute(&mut result, ATTRIBUTE_NAME_STANDALONE, standalone);
result.extend("?>".encode_utf16());
Utf16String::from_utf16(result)
}
fn append_attribute(result: &mut Vec<u16>, name: &str, value: Option<&Utf16String>) {
let Some(value) = value else {
return;
};
result.push(u16::from(b' '));
result.extend(name.encode_utf16());
result.extend("=\"".encode_utf16());
result.extend_from_slice(value.as_utf16());
result.push(u16::from(b'"'));
}
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()),
}
}