polydat_nodes/
encoding.rs1#[cfg(test)]
7use polydat::ast::Value;
8
9#[polydat::polydat_node(category = Encoding)]
15fn html_encode(input: String) -> String {
16 let mut result = String::with_capacity(input.len());
17 for c in input.chars() {
18 match c {
19 '&' => result.push_str("&"),
20 '<' => result.push_str("<"),
21 '>' => result.push_str(">"),
22 '"' => result.push_str("""),
23 '\'' => result.push_str("'"),
24 _ => result.push(c),
25 }
26 }
27 result
28}
29
30#[polydat::polydat_node(category = Encoding)]
32fn html_decode(input: String) -> String {
33 input
34 .replace("&", "&")
35 .replace("<", "<")
36 .replace(">", ">")
37 .replace(""", "\"")
38 .replace("'", "'")
39 .replace("'", "'")
40}
41
42fn is_url_unreserved(b: u8) -> bool {
43 b.is_ascii_alphanumeric() || b == b'-' || b == b'_' || b == b'.' || b == b'~'
44}
45
46#[polydat::polydat_node(category = Encoding)]
48fn url_encode(input: String) -> String {
49 let mut result = String::with_capacity(input.len());
50 for &b in input.as_bytes() {
51 if is_url_unreserved(b) {
52 result.push(b as char);
53 } else {
54 result.push_str(&format!("%{b:02X}"));
55 }
56 }
57 result
58}
59
60#[polydat::polydat_node(category = Encoding)]
62fn url_decode(input: String) -> String {
63 let bytes = input.as_bytes();
64 let mut result = Vec::with_capacity(bytes.len());
65 let mut i = 0;
66 while i < bytes.len() {
67 if bytes[i] == b'%'
68 && i + 2 < bytes.len()
69 && let Ok(byte) = u8::from_str_radix(&input[i + 1..i + 3], 16)
70 {
71 result.push(byte);
72 i += 3;
73 continue;
74 }
75 result.push(bytes[i]);
76 i += 1;
77 }
78 String::from_utf8_lossy(&result).into_owned()
79}
80
81#[cfg(test)]
82mod tests {
83 use super::*;
84 use polydat::ast::PolydatNode;
85
86 #[test]
87 fn html_encode_basic() {
88 let node = HtmlEncode::new();
89 let mut out = [Value::None];
90 node.eval(&[Value::Str("<b>hello & world</b>".into())], &mut out);
91 assert_eq!(out[0].as_str(), "<b>hello & world</b>");
92 }
93
94 #[test]
95 fn html_encode_quotes() {
96 let node = HtmlEncode::new();
97 let mut out = [Value::None];
98 node.eval(&[Value::Str(r#"say "hello" it's fine"#.into())], &mut out);
99 assert_eq!(out[0].as_str(), "say "hello" it's fine");
100 }
101
102 #[test]
103 fn html_encode_passthrough() {
104 let node = HtmlEncode::new();
105 let mut out = [Value::None];
106 node.eval(&[Value::Str("plain text 123".into())], &mut out);
107 assert_eq!(out[0].as_str(), "plain text 123");
108 }
109
110 #[test]
111 fn html_roundtrip() {
112 let enc = HtmlEncode::new();
113 let dec = HtmlDecode::new();
114 let mut mid = [Value::None];
115 let mut out = [Value::None];
116 let input = "<div class=\"test\">hello & 'world'</div>";
117 enc.eval(&[Value::Str(input.into())], &mut mid);
118 dec.eval(&[mid[0].clone()], &mut out);
119 assert_eq!(out[0].as_str(), input);
120 }
121
122 #[test]
123 fn url_encode_basic() {
124 let node = UrlEncode::new();
125 let mut out = [Value::None];
126 node.eval(&[Value::Str("hello world".into())], &mut out);
127 assert_eq!(out[0].as_str(), "hello%20world");
128 }
129
130 #[test]
131 fn url_encode_special() {
132 let node = UrlEncode::new();
133 let mut out = [Value::None];
134 node.eval(&[Value::Str("a=1&b=2".into())], &mut out);
135 assert_eq!(out[0].as_str(), "a%3D1%26b%3D2");
136 }
137
138 #[test]
139 fn url_encode_passthrough() {
140 let node = UrlEncode::new();
141 let mut out = [Value::None];
142 node.eval(&[Value::Str("hello-world_123.txt~".into())], &mut out);
143 assert_eq!(out[0].as_str(), "hello-world_123.txt~");
144 }
145
146 #[test]
147 fn url_roundtrip() {
148 let enc = UrlEncode::new();
149 let dec = UrlDecode::new();
150 let mut mid = [Value::None];
151 let mut out = [Value::None];
152 let input = "hello world & friends = cool";
153 enc.eval(&[Value::Str(input.into())], &mut mid);
154 dec.eval(&[mid[0].clone()], &mut out);
155 assert_eq!(out[0].as_str(), input);
156 }
157}