#[cfg(test)]
use crate::ast::Value;
#[crate::polydat_node(category = Encoding)]
fn html_encode(input: String) -> String {
let mut result = String::with_capacity(input.len());
for c in input.chars() {
match c {
'&' => result.push_str("&"),
'<' => result.push_str("<"),
'>' => result.push_str(">"),
'"' => result.push_str("""),
'\'' => result.push_str("'"),
_ => result.push(c),
}
}
result
}
#[crate::polydat_node(category = Encoding)]
fn html_decode(input: String) -> String {
input
.replace("&", "&")
.replace("<", "<")
.replace(">", ">")
.replace(""", "\"")
.replace("'", "'")
.replace("'", "'")
}
fn is_url_unreserved(b: u8) -> bool {
b.is_ascii_alphanumeric() || b == b'-' || b == b'_' || b == b'.' || b == b'~'
}
#[crate::polydat_node(category = Encoding)]
fn url_encode(input: String) -> String {
let mut result = String::with_capacity(input.len());
for &b in input.as_bytes() {
if is_url_unreserved(b) {
result.push(b as char);
} else {
result.push_str(&format!("%{b:02X}"));
}
}
result
}
#[crate::polydat_node(category = Encoding)]
fn url_decode(input: String) -> String {
let bytes = input.as_bytes();
let mut result = Vec::with_capacity(bytes.len());
let mut i = 0;
while i < bytes.len() {
if bytes[i] == b'%' && i + 2 < bytes.len()
&& let Ok(byte) = u8::from_str_radix(
&input[i + 1..i + 3], 16
) {
result.push(byte);
i += 3;
continue;
}
result.push(bytes[i]);
i += 1;
}
String::from_utf8_lossy(&result).into_owned()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ast::PolydatNode;
#[test]
fn html_encode_basic() {
let node = HtmlEncode::new();
let mut out = [Value::None];
node.eval(&[Value::Str("<b>hello & world</b>".into())], &mut out);
assert_eq!(out[0].as_str(), "<b>hello & world</b>");
}
#[test]
fn html_encode_quotes() {
let node = HtmlEncode::new();
let mut out = [Value::None];
node.eval(&[Value::Str(r#"say "hello" it's fine"#.into())], &mut out);
assert_eq!(out[0].as_str(), "say "hello" it's fine");
}
#[test]
fn html_encode_passthrough() {
let node = HtmlEncode::new();
let mut out = [Value::None];
node.eval(&[Value::Str("plain text 123".into())], &mut out);
assert_eq!(out[0].as_str(), "plain text 123");
}
#[test]
fn html_roundtrip() {
let enc = HtmlEncode::new();
let dec = HtmlDecode::new();
let mut mid = [Value::None];
let mut out = [Value::None];
let input = "<div class=\"test\">hello & 'world'</div>";
enc.eval(&[Value::Str(input.into())], &mut mid);
dec.eval(&[mid[0].clone()], &mut out);
assert_eq!(out[0].as_str(), input);
}
#[test]
fn url_encode_basic() {
let node = UrlEncode::new();
let mut out = [Value::None];
node.eval(&[Value::Str("hello world".into())], &mut out);
assert_eq!(out[0].as_str(), "hello%20world");
}
#[test]
fn url_encode_special() {
let node = UrlEncode::new();
let mut out = [Value::None];
node.eval(&[Value::Str("a=1&b=2".into())], &mut out);
assert_eq!(out[0].as_str(), "a%3D1%26b%3D2");
}
#[test]
fn url_encode_passthrough() {
let node = UrlEncode::new();
let mut out = [Value::None];
node.eval(&[Value::Str("hello-world_123.txt~".into())], &mut out);
assert_eq!(out[0].as_str(), "hello-world_123.txt~");
}
#[test]
fn url_roundtrip() {
let enc = UrlEncode::new();
let dec = UrlDecode::new();
let mut mid = [Value::None];
let mut out = [Value::None];
let input = "hello world & friends = cool";
enc.eval(&[Value::Str(input.into())], &mut mid);
dec.eval(&[mid[0].clone()], &mut out);
assert_eq!(out[0].as_str(), input);
}
}