///|
/// Controls the autoescaping behavior.
pub(all) enum AutoEscape {
/// Do not apply auto escaping.
NoEscape
/// Use HTML auto escaping rules.
Html
/// Use escaping rules suitable for JSON/JavaScript or YAML.
Json
/// A custom auto escape format. The default formatter fails for it.
Custom(String)
} derive(Eq)
///|
fn AutoEscape::debug_name(self : AutoEscape) -> String {
match self {
NoEscape => "None"
Html => "Html"
Json => "Json"
Custom(name) => "Custom(\{@rfmt.str_debug(name)})"
}
}
///|
/// Defines the behavior of undefined values in the engine.
pub(all) enum UndefinedBehavior {
/// The default, somewhat lenient undefined behavior.
///
/// * **printing:** allowed (returns empty string)
/// * **iteration:** allowed (returns empty array)
/// * **attribute access of undefined values:** fails
/// * **if true:** allowed (is considered false)
Lenient
/// Like `Lenient`, but also allows chaining of undefined lookups.
Chainable
/// Like strict, but does not error when the undefined is checked for
/// truthiness.
SemiStrict
/// Complains very quickly about undefined values.
Strict
} derive(Eq)
///|
fn UndefinedBehavior::handle_undefined(
self : UndefinedBehavior,
parent_was_undefined : Bool,
) -> Value raise TemplateError {
match (self, parent_was_undefined) {
(Lenient, false) | (Strict, false) | (SemiStrict, false) | (Chainable, _) =>
Value::undefined()
(Lenient, true) | (Strict, true) | (SemiStrict, true) =>
raise TemplateError::from_kind(UndefinedError)
}
}
///|
fn UndefinedBehavior::is_true(
self : UndefinedBehavior,
value : Value,
) -> Bool raise TemplateError {
match (self, value) {
// silent undefined doesn't error, even in strict mode
(Strict, Undefined(Default)) =>
raise TemplateError::from_kind(UndefinedError)
_ => value.is_true()
}
}
///|
fn UndefinedBehavior::try_iter(
self : UndefinedBehavior,
value : Value,
) -> Iter[Value] raise TemplateError {
self.assert_iterable(value)
value.try_iter()
}
///|
fn UndefinedBehavior::assert_iterable(
self : UndefinedBehavior,
value : Value,
) -> Unit raise TemplateError {
match (self, value) {
(Strict | SemiStrict, Undefined(Default)) =>
raise TemplateError::from_kind(UndefinedError)
_ => ()
}
}
///|
fn UndefinedBehavior::assert_value_not_undefined(
self : UndefinedBehavior,
value : Value,
) -> Unit raise TemplateError {
match (self, value) {
(Strict | SemiStrict, Undefined(Default)) =>
raise TemplateError::from_kind(UndefinedError)
_ => ()
}
}
///|
fn needs_html_escaping(s : String) -> Bool {
for i in 0..' | '&' | '"' | '\'' | '/' => return true
_ => ()
}
}
false
}
///|
/// HTML escapes a string (`<`, `>`, `&`, `"`, `'` and `/`).
pub fn html_escape(s : String) -> String {
if !needs_html_escaping(s) {
return s
}
let sb = StringBuilder()
write_html_escaped(sb, s)
sb.to_string()
}
///|
fn write_html_escaped(sb : StringBuilder, s : String) -> Unit {
let mut start = 0
for i in 0.. "<"
'>' => ">"
'&' => "&"
'"' => """
'\'' => "'"
'/' => "/"
_ => continue
}
if start < i {
sb.write_view(s.view(start_offset=start, end_offset=i))
}
sb.write_string(rep)
start = i + 1
}
if start < s.length() {
sb.write_view(s.view(start_offset=start))
}
}
///|
fn is_ascii_integer_str(s : String) -> Bool {
if s.is_empty() {
return false
}
let start = if s[0] == '-' { 1 } else { 0 }
if start == s.length() {
return false
}
for i in start..= '0' && s[i] <= '9') {
return false
}
}
true
}
///|
fn write_with_html_escaping(
out : Output,
value : Value,
) -> Unit raise TemplateError {
match value {
U64(v) => return out.write_str(v.to_string())
I64(v) => return out.write_str(v.to_string())
Bool(v) => return out.write_str(if v { "True" } else { "False" })
_ => ()
}
match value.as_str() {
Some(s) =>
if is_ascii_integer_str(s) || !needs_html_escaping(s) {
out.write_str(s)
} else {
out.write_str(html_escape(s))
}
None =>
if value.kind() is (Undefined | Null | Bool | Number) {
out.write_str(value.to_string())
} else {
out.write_str(html_escape(value.to_string()))
}
}
}
///|
/// Writes a value to the output with the given auto escaping applied.
fn write_escaped(
out : Output,
auto_escape : AutoEscape,
value : Value,
) -> Unit raise TemplateError {
// safe strings bypass all of this
if value is Str(s, Safe) {
return out.write_str(s)
}
match auto_escape {
NoEscape => out.write_str(value.to_string())
Html => write_with_html_escaping(out, value)
Json => {
let s = value_to_json_with_style(value, Compact) catch {
err =>
raise TemplateError::new(BadSerialization, "unable to format to JSON").with_source(
err,
)
}
out.write_str(s)
}
Custom(name) =>
raise TemplateError::new(
InvalidOperation,
"Default formatter does not know how to format to custom format '\{name}'",
)
}
}