///|
fn serializable_ascii_alpha(ch : Char) -> Bool {
(ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z')
}
///|
fn serializable_ascii_digit(ch : Char) -> Bool {
ch >= '0' && ch <= '9'
}
///|
fn serializable_tag_name_char(ch : Char) -> Bool {
serializable_ascii_alpha(ch) ||
serializable_ascii_digit(ch) ||
ch == ':' ||
ch == '_' ||
ch == '-'
}
///|
fn serializable_attr_name_start(ch : Char) -> Bool {
serializable_ascii_alpha(ch) || ch == '_' || ch == ':'
}
///|
fn serializable_attr_name_char(ch : Char) -> Bool {
serializable_attr_name_start(ch) ||
serializable_ascii_digit(ch) ||
ch == '-' ||
ch == '.'
}
///|
fn validate_serializable_tag_name(
name : StringView,
) -> Unit raise @core.HtmlError {
match name.get_char(0) {
Some(ch) if serializable_ascii_alpha(ch) => {
let mut pos = ch.utf16_len()
while pos < name.length() {
match name.get_char(pos) {
Some(ch) if serializable_tag_name_char(ch) => pos += ch.utf16_len()
_ =>
raise InvalidSerialization(
"Unsafe element name for serialization: " + name.to_owned(),
)
}
}
}
_ =>
raise InvalidSerialization(
"Unsafe element name for serialization: " + name.to_owned(),
)
}
}
///|
fn validate_serializable_attr_name(
name : StringView,
) -> Unit raise @core.HtmlError {
match name.get_char(0) {
Some(ch) if serializable_attr_name_start(ch) => {
let mut pos = ch.utf16_len()
while pos < name.length() {
match name.get_char(pos) {
Some(ch) if serializable_attr_name_char(ch) => pos += ch.utf16_len()
_ =>
raise InvalidSerialization(
"Unsafe attribute name for serialization: " + name.to_owned(),
)
}
}
}
_ =>
raise InvalidSerialization(
"Unsafe attribute name for serialization: " + name.to_owned(),
)
}
}
///|
fn attr_value_should_minimize(name : StringView, value : StringView) -> Bool {
value.is_empty() || value.compare_ignore_ascii_case(name) == 0
}
///|
fn choose_attr_quote(value : StringView, preferred : Char) -> Char {
if preferred == '\'' {
'\''
} else if string_contains_char(value, '"') &&
!string_contains_char(value, '\'') {
'\''
} else {
'"'
}
}
///|
fn validate_serialization_quote(quote : Char) -> Char raise @core.HtmlError {
match quote {
'"' | '\'' => quote
_ => raise InvalidSerialization("quote must be ' or \"")
}
}
///|
/// Serialize a start tag without validating element or attribute names.
///
/// Attribute values are escaped and may use the requested quote character, but
/// callers must ensure `node.name` and all attribute names are already safe for
/// serialization. Use the checked serializer path for untrusted names.
pub fn serialize_start_tag_unchecked(node : @dom.Node, quote : Char) -> String {
let out = StringBuilder::new()
out.write_char('<')
out.write_string(node.name)
for name, value in node.attrs {
out.write_char(' ')
out.write_string(name)
match value {
Some(v) if attr_value_should_minimize(name, v) => ()
Some(v) => {
let attr_quote = choose_attr_quote(v, quote)
out.write_char('=')
out.write_char(attr_quote)
out.write_string(escape_attr_value(v, attr_quote))
out.write_char(attr_quote)
}
None => ()
}
}
out.write_char('>')
out.to_string()
}
///|
fn serialize_start_tag(
node : @dom.Node,
quote : Char,
) -> String raise @core.HtmlError {
validate_serializable_tag_name(node.name)
for name, _ in node.attrs {
validate_serializable_attr_name(name)
}
serialize_start_tag_unchecked(node, quote)
}