use compact_str::CompactString;
use rustc_hash::FxHashMap;
use smallvec::SmallVec;
use super::{parser::HtmlElement, FrameworkComponentIsland};
pub(super) fn find_island<'a>(
element: &HtmlElement,
islands: &'a [FrameworkComponentIsland],
) -> Option<&'a FrameworkComponentIsland> {
let island_id = get_attribute_value(element, "data-ox-id")
.or_else(|| get_attribute_value(element, "dataOxId"))?;
islands.iter().find(|island| island.id == island_id)
}
pub(super) fn push_attribute_value(output: &mut String, value: Option<&str>) {
if let Some(value) = value {
push_js_string_literal(output, value);
} else {
output.push_str("true");
}
}
pub(super) fn render_json_object_literal(value: &FxHashMap<String, serde_json::Value>) -> String {
let mut output = String::with_capacity(value.len().saturating_mul(24).max(4));
push_json_object_literal(&mut output, value);
output
}
pub(super) fn push_json_object_literal(
output: &mut String,
value: &FxHashMap<String, serde_json::Value>,
) {
if value.is_empty() {
output.push_str("null");
return;
}
let mut properties = SmallVec::<[(&String, &serde_json::Value); 8]>::with_capacity(value.len());
properties.extend(value.iter());
properties.sort_unstable_by_key(|(key, _)| *key);
output.push_str("{ ");
for (index, (key, value)) in properties.iter().enumerate() {
if index > 0 {
output.push_str(", ");
}
push_js_string_literal(output, key);
output.push_str(": ");
output.push_str(&json_value_literal(value));
}
output.push_str(" }");
}
pub(super) fn push_object_entry_key(output: &mut String, has_entries: &mut bool, key: &str) {
if *has_entries {
output.push_str(", ");
} else {
output.push_str("{ ");
*has_entries = true;
}
push_js_string_literal(output, key);
output.push_str(": ");
}
pub(super) fn finish_object_literal(output: &mut String, has_entries: bool) {
if has_entries {
output.push_str(" }");
} else {
output.push_str("null");
}
}
pub(super) fn should_skip_property(name: &str) -> bool {
name.starts_with("data-ox-")
}
pub(super) fn to_data_or_aria_attribute_name(name: &str) -> CompactString {
if (name.starts_with("data") || name.starts_with("aria"))
&& name.bytes().any(|byte| byte.is_ascii_uppercase())
{
camel_to_kebab(name)
} else {
CompactString::from(name)
}
}
pub(super) fn js_string_literal(value: &str) -> String {
let mut output = String::with_capacity(value.len() + 2);
push_js_string_literal(&mut output, value);
output
}
pub(super) fn push_js_string_literal(output: &mut String, value: &str) {
push_js_string_literal_with_options(output, value, false);
}
pub(super) fn push_raw_html_js_string_literal(output: &mut String, value: &str) {
push_js_string_literal_with_options(output, value, true);
}
fn push_js_string_literal_with_options(output: &mut String, value: &str, escape_markup: bool) {
output.push('"');
for ch in value.chars() {
match ch {
'"' => output.push_str("\\\""),
'\\' => output.push_str("\\\\"),
'<' if escape_markup => output.push_str("\\x3C"),
'\n' => output.push_str("\\n"),
'\r' => output.push_str("\\r"),
'\t' => output.push_str("\\t"),
'\u{08}' => output.push_str("\\b"),
'\u{0c}' => output.push_str("\\f"),
'\u{2028}' => output.push_str("\\u2028"),
'\u{2029}' => output.push_str("\\u2029"),
ch if ch.is_control() => push_json_unicode_escape(output, ch),
ch => output.push(ch),
}
}
output.push('"');
}
fn camel_to_kebab(value: &str) -> CompactString {
let mut output = CompactString::with_capacity(value.len());
for ch in value.chars() {
if ch.is_ascii_uppercase() {
output.push('-');
output.push(ch.to_ascii_lowercase());
} else {
output.push(ch);
}
}
output
}
fn json_value_literal(value: &serde_json::Value) -> String {
serde_json::to_string(value).unwrap_or_else(|_| String::from("null"))
}
fn push_json_unicode_escape(output: &mut String, ch: char) {
const HEX: &[u8; 16] = b"0123456789abcdef";
let value = ch as u32;
output.push_str("\\u");
output.push(HEX[((value >> 12) & 0xf) as usize] as char);
output.push(HEX[((value >> 8) & 0xf) as usize] as char);
output.push(HEX[((value >> 4) & 0xf) as usize] as char);
output.push(HEX[(value & 0xf) as usize] as char);
}
fn get_attribute_value<'a>(element: &'a HtmlElement, name: &str) -> Option<&'a str> {
element
.attributes
.iter()
.find(|attribute| attribute.name == name)
.and_then(|attribute| attribute.value.as_deref())
}