mtml_parser/
serializer.rs1extern crate clap;
2use clap::{Parser, ValueEnum};
3
4use super::ast::{Node::*, *};
5
6#[derive(Debug, Clone, PartialEq, Eq, ValueEnum)]
7pub enum FunctionTagStyle {
8 Dollar = 1,
9 SelfClosing,
10}
11
12#[derive(Debug, Clone, Parser)]
13#[command(author, version, about, long_about = None)]
14pub struct Options {
15 #[arg(short, long, default_value = "mt:")]
16 pub prefix: String,
17 #[arg(short, long, value_enum, default_value = "dollar")]
18 pub function_tag_style: FunctionTagStyle,
19}
20
21fn attribute_to_string(attr: Attribute) -> String {
22 format!(
23 " {}={}",
24 attr.name,
25 attr.values
26 .iter()
27 .map({
28 |AttributeValue { value, .. }| {
29 if value.contains("\"") {
30 format!("'{}'", value)
31 } else {
32 format!(r#""{}""#, value)
33 }
34 }
35 })
36 .collect::<Vec<String>>()
37 .join(",")
38 )
39}
40
41pub fn serialize(node: Node, options: Option<Options>) -> String {
55 let mut s = String::new();
56 let options = options.unwrap_or(Options {
57 prefix: "mt:".to_string(),
58 function_tag_style: FunctionTagStyle::Dollar,
59 });
60
61 match node {
62 Root(RootNode { children }) => {
63 for child in children {
64 s.push_str(&serialize(child, Some(options.clone())));
65 }
66 }
67 Text(TextNode { value, .. }) => {
68 s.push_str(value.as_str());
69 }
70 FunctionTag(FunctionTagNode {
71 name, attributes, ..
72 }) => {
73 let pre_sign = if name.to_lowercase() == "else" || name.to_lowercase() == "elseif" {
74 ""
75 } else if options.function_tag_style == FunctionTagStyle::Dollar {
76 "$"
77 } else {
78 ""
79 };
80
81 let post_sign = if name.to_lowercase() == "else" || name.to_lowercase() == "elseif" {
82 ""
83 } else if options.function_tag_style == FunctionTagStyle::Dollar {
84 "$"
85 } else {
86 "/"
87 };
88
89 s.push_str(&format!("<{}{}{}", pre_sign, options.prefix, name));
90 for attr in attributes {
91 s.push_str(&attribute_to_string(attr))
92 }
93 s.push_str(&format!("{}>", post_sign));
94 }
95 BlockTag(BlockTagNode {
96 name,
97 children,
98 attributes,
99 ..
100 }) => {
101 s.push_str(&format!("<{}{}", options.prefix, name));
102 for attr in attributes {
103 s.push_str(&attribute_to_string(attr))
104 }
105 s.push_str(">");
106 for child in children {
107 s.push_str(&serialize(child, Some(options.clone())));
108 }
109 s.push_str(&format!("</{}{}>", options.prefix, name));
110 }
111 }
112
113 return s;
114}
115
116#[cfg(test)]
117mod tests {
118 use super::super::parser::*;
119 use super::*;
120
121 const INPUT: &str = r#"
122<html>
123 <body>
124 <mt:Entries limit="10">
125 <mtEntryTitle encode_html='1'/>
126 </mt:Entries>
127 </body>
128</html>"#;
129
130 #[test]
131 fn test_serialize() {
132 let root = parse(INPUT).unwrap();
133 let serialized = serialize(root, None);
134 assert_eq!(
135 serialized,
136 r#"
137<html>
138 <body>
139 <mt:Entries limit="10">
140 <$mt:EntryTitle encode_html="1"$>
141 </mt:Entries>
142 </body>
143</html>"#
144 )
145 }
146
147 #[test]
148 fn test_serialize_self_closing() {
149 let root = parse(INPUT).unwrap();
150 let serialized = serialize(
151 root,
152 Some(Options {
153 prefix: "mt:".to_string(),
154 function_tag_style: FunctionTagStyle::SelfClosing,
155 }),
156 );
157 assert_eq!(
158 serialized,
159 r#"
160<html>
161 <body>
162 <mt:Entries limit="10">
163 <mt:EntryTitle encode_html="1"/>
164 </mt:Entries>
165 </body>
166</html>"#
167 )
168 }
169
170 #[test]
171 fn test_serialize_prefix() {
172 let root = parse(INPUT).unwrap();
173 let serialized = serialize(
174 root,
175 Some(Options {
176 prefix: "MT".to_string(),
177 function_tag_style: FunctionTagStyle::Dollar,
178 }),
179 );
180 assert_eq!(
181 serialized,
182 r#"
183<html>
184 <body>
185 <MTEntries limit="10">
186 <$MTEntryTitle encode_html="1"$>
187 </MTEntries>
188 </body>
189</html>"#
190 )
191 }
192
193 #[test]
194 fn test_serialize_single_quote() {
195 let root = parse(
196 r#"
197<html>
198 <body>
199 <mt:Entries limit="10">
200 <mtEntryTitle replace='"',"'"/>
201 </mt:Entries>
202 </body>
203</html>"#,
204 )
205 .unwrap();
206 let serialized = serialize(
207 root,
208 Some(Options {
209 prefix: "mt:".to_string(),
210 function_tag_style: FunctionTagStyle::Dollar,
211 }),
212 );
213 assert_eq!(
214 serialized,
215 r#"
216<html>
217 <body>
218 <mt:Entries limit="10">
219 <$mt:EntryTitle replace='"',"'"$>
220 </mt:Entries>
221 </body>
222</html>"#
223 )
224 }
225
226 #[test]
227 fn test_serialize_else_tag() {
228 let root = parse(
229 r#"
230<html>
231 <body>
232 <mt:If name="foo">
233 Foo
234 <mt:ElseIf name="bar">
235 Bar
236 <mt:Else>
237 Baz
238 </mt:If>
239 </body>
240</html>"#,
241 )
242 .unwrap();
243 let serialized = serialize(root, None);
244 assert_eq!(
245 serialized,
246 r#"
247<html>
248 <body>
249 <mt:If name="foo">
250 Foo
251 <mt:ElseIf name="bar">
252 Bar
253 <mt:Else>
254 Baz
255 </mt:If>
256 </body>
257</html>"#
258 )
259 }
260}