use std::fmt::{Display, Formatter};
use thiserror::Error;
use crate::util::Utf16String;
#[allow(clippy::upper_case_acronyms)]
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum StandardInlineMode {
NONE,
HTML,
XML,
TEXT,
JAVASCRIPT,
CSS,
}
impl StandardInlineMode {
pub const VALUES: [Self; 6] = [
Self::NONE,
Self::HTML,
Self::XML,
Self::TEXT,
Self::JAVASCRIPT,
Self::CSS,
];
pub fn parse(mode: Option<&Utf16String>) -> Result<Self, StandardInlineModeParseError> {
let Some(mode) = mode else {
return Err(StandardInlineModeParseError::NullOrEmpty);
};
if trim(mode.as_utf16()).is_empty() {
return Err(StandardInlineModeParseError::NullOrEmpty);
}
for candidate in Self::VALUES {
if equals_ignore_case_ascii(mode.as_utf16(), candidate.name().as_bytes()) {
return Ok(candidate);
}
}
Err(StandardInlineModeParseError::Unrecognized(mode.clone()))
}
#[must_use]
pub const fn ordinal(self) -> usize {
match self {
Self::NONE => 0,
Self::HTML => 1,
Self::XML => 2,
Self::TEXT => 3,
Self::JAVASCRIPT => 4,
Self::CSS => 5,
}
}
const fn name(self) -> &'static str {
match self {
Self::NONE => "NONE",
Self::HTML => "HTML",
Self::XML => "XML",
Self::TEXT => "TEXT",
Self::JAVASCRIPT => "JAVASCRIPT",
Self::CSS => "CSS",
}
}
}
impl Display for StandardInlineMode {
fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
formatter.write_str(self.name())
}
}
#[derive(Clone, Debug, Eq, Error, PartialEq)]
pub enum StandardInlineModeParseError {
#[error("Inline mode cannot be null or empty")]
NullOrEmpty,
#[error("Unrecognized inline mode: {}", .0.to_string_lossy())]
Unrecognized(Utf16String),
}
impl StandardInlineModeParseError {
#[must_use]
pub const fn class_name(&self) -> &'static str {
"java.lang.IllegalArgumentException"
}
#[must_use]
pub fn message(&self) -> Utf16String {
match self {
Self::NullOrEmpty => Utf16String::from_rust_str("Inline mode cannot be null or empty"),
Self::Unrecognized(mode) => {
let mut message = Utf16String::from_rust_str("Unrecognized inline mode: ")
.as_utf16()
.to_vec();
message.extend_from_slice(mode.as_utf16());
Utf16String::from_utf16(message)
}
}
}
}
fn trim(units: &[u16]) -> &[u16] {
let start = units
.iter()
.position(|unit| *unit > 0x0020)
.unwrap_or(units.len());
let end = units
.iter()
.rposition(|unit| *unit > 0x0020)
.map_or(start, |position| position + 1);
&units[start..end]
}
fn equals_ignore_case_ascii(actual: &[u16], expected: &[u8]) -> bool {
actual.len() == expected.len()
&& actual
.iter()
.zip(expected)
.all(|(actual, expected)| code_unit_equals_ascii_ignore_case(*actual, *expected))
}
fn code_unit_equals_ascii_ignore_case(actual: u16, expected: u8) -> bool {
let expected = u16::from(expected);
actual == expected
|| (expected >= u16::from(b'A') && expected <= u16::from(b'Z') && actual == expected + 0x20)
|| (expected == u16::from(b'I') && matches!(actual, 0x0130 | 0x0131))
|| (expected == u16::from(b'S') && actual == 0x017F)
}
#[cfg(test)]
mod tests {
use super::{StandardInlineMode, StandardInlineModeParseError};
use crate::util::Utf16String;
#[test]
fn preserves_values_ordinals_display_and_ascii_case_parsing() {
for (ordinal, value) in StandardInlineMode::VALUES.into_iter().enumerate() {
assert_eq!(value.ordinal(), ordinal);
assert_eq!(
StandardInlineMode::parse(Some(&Utf16String::from_rust_str(
&value.to_string().to_ascii_lowercase()
))),
Ok(value)
);
}
assert_eq!(StandardInlineMode::NONE.to_string(), "NONE");
assert_eq!(StandardInlineMode::HTML.to_string(), "HTML");
assert_eq!(StandardInlineMode::XML.to_string(), "XML");
assert_eq!(StandardInlineMode::TEXT.to_string(), "TEXT");
assert_eq!(StandardInlineMode::JAVASCRIPT.to_string(), "JAVASCRIPT");
assert_eq!(StandardInlineMode::CSS.to_string(), "CSS");
}
#[test]
fn preserves_java_trim_unicode_case_and_error_messages() {
for input in [
None,
Some(Utf16String::from_rust_str("")),
Some(Utf16String::from_utf16([0x0000, 0x0020])),
] {
assert_eq!(
StandardInlineMode::parse(input.as_ref()),
Err(StandardInlineModeParseError::NullOrEmpty)
);
}
assert_eq!(
StandardInlineMode::parse(Some(&Utf16String::from_utf16([
b'C' as u16,
0x017F,
0x017F,
]))),
Ok(StandardInlineMode::CSS)
);
assert_eq!(
StandardInlineMode::parse(Some(&Utf16String::from_rust_str(" HTML "))),
Err(StandardInlineModeParseError::Unrecognized(
Utf16String::from_rust_str(" HTML ")
))
);
assert_eq!(
StandardInlineModeParseError::NullOrEmpty.to_string(),
"Inline mode cannot be null or empty"
);
assert_eq!(
StandardInlineModeParseError::Unrecognized(Utf16String::from_rust_str("RAW"))
.to_string(),
"Unrecognized inline mode: RAW"
);
assert_eq!(
StandardInlineModeParseError::NullOrEmpty.class_name(),
"java.lang.IllegalArgumentException"
);
assert_eq!(
StandardInlineModeParseError::NullOrEmpty.message(),
Utf16String::from_rust_str("Inline mode cannot be null or empty")
);
assert_eq!(
StandardInlineModeParseError::Unrecognized(Utf16String::from_utf16([0xD800]))
.message()
.as_utf16()
.last(),
Some(&0xD800)
);
}
}