1use regex::RegexBuilder;
2
3use crate::*;
4
5#[derive(PartialEq, Eq)]
6enum PassageState {
7 Title,
8 Tags,
9 Between,
10}
11
12pub fn parse_twee3(source: &str) -> Result<(Story, Vec<Warning>), Error> {
14 let re = RegexBuilder::new("^::[^\n]*\n").multi_line(true).build().unwrap();
15 let mut warnings = vec![];
16 let mut passages: Vec<Passage> = Vec::new();
17 let mut start = 0;
18 let mut name = Vec::<char>::new();
19 let mut tags = Vec::<String>::new();
20 let mut meta: &str = "{}";
21 let mut title = String::new();
22 let mut story_meta = None;
23 fn handle_passage(warnings: &mut Vec<Warning>, title: &mut String, story_meta: &mut Option<Map<String, Value>>, passages: &mut Vec<Passage>, name: &str, content: &str, tags: &Vec<String>, meta: &str) {
24 if name.len() == 0 {
25 warnings.push(Warning::PassageNameMissing);
26 } else {
27 match name {
28 "StoryTitle" => {
29 if title.len() != 0 {
30 warnings.push(Warning::PassageDuplicated("StoryTitle".to_string()));
31 }
32 *title = content.trim().to_string();
33 },
34 "StoryData" => {
35 if story_meta.is_some() {
36 warnings.push(Warning::PassageDuplicated("StoryData".to_string()));
37 }
38 *story_meta = if let Ok(v) = serde_json::from_str(&content) {
39 let v: Value = v;
40 match v {
41 Value::Object(o) => {
42 Some(o)
43 },
44 _ => {
45 warnings.push(Warning::StoryMetadataMalformed);
46 Some(Map::new())
47 }
48 }
49 } else {
50 warnings.push(Warning::StoryMetadataMalformed);
51 Some(Map::new())
52 };
53 },
54 _ => {
55 let mut dup = false;
56 for p in &mut *passages {
57 if p.name == name {
58 warnings.push(Warning::PassageDuplicated(p.name.clone()));
59 dup = true;
60 break;
61 }
62 }
63 if ! dup {
64 let meta = if let Ok(v) = serde_json::from_str(meta) {
65 let v: Value = v;
66 match v {
67 Value::Object(o) => {
68 o
69 },
70 _ => {
71 warnings.push(Warning::PassageMetadataMalformed(name.to_string()));
72 Map::new()
73 }
74 }
75 } else {
76 warnings.push(Warning::PassageMetadataMalformed(name.to_string()));
77 Map::new()
78 };
79 passages.push(Passage { name: name.to_string(), tags: tags.clone(), meta, content: content.trim_end().to_string()});
80 }
81 }
82 }
83 }
84 }
85 while let Some(a) = re.find_at(source, start) {
86 if start != 0 {
87 let name: String = name.iter().collect();
88 let name = name.trim().to_string();
89 let mut content = source[start..(a.start())].to_string();
90 if content.starts_with("\\::") {
91 content.remove(0);
92 }
93 handle_passage(&mut warnings, &mut title, &mut story_meta, &mut passages, &name, &content, &tags, meta);
94 }
95 start = a.start() + 2;
96 name.clear();
97 tags.clear();
98 meta = "{}";
99 let mut tag = Vec::<char>::new();
100 let mut state = PassageState::Title;
101 let mut escape = false;
102 for (i, c) in source[start..].char_indices() {
103 if ['\r', '\n'].contains(&c) {
104 break;
105 }
106 match state {
107 PassageState::Title => {
108 if escape {
109 escape = false;
110 name.push(c);
111 continue;
112 }
113 if c == '[' {
114 state = PassageState::Tags;
115 continue;
116 }
117 if c == '{' {
118 let i = start + i;
119 meta = &source[if let Some(newline) = source[i..].find("\n") {
120 i..(i + newline)
121 } else {
122 i..source.len()
123 }];
124 break;
125 }
126 if c == '\\' {
127 escape = true;
128 continue;
129 }
130 name.push(c);
131 },
132 PassageState::Tags => {
133 if escape {
134 escape = false;
135 tag.push(c);
136 continue;
137 }
138 if c == '\\' {
139 escape = true;
140 continue;
141 }
142 if c == ']' {
143 if ! tag.is_empty() {
144 tags.push(tag.iter().collect());
145 }
146 state = PassageState::Between;
147 continue;
148 }
149 if c.is_whitespace() && ! tag.is_empty() {
150 tags.push(tag.iter().collect());
151 tag = vec![];
152 } else {
153 tag.push(c);
154 }
155 },
156 PassageState::Between => {
157 if c == '{' {
158 let i = start + i;
159 meta = &source[if let Some(newline) = source[i..].find("\n") {
160 i..(i + newline)
161 } else {
162 i..source.len()
163 }];
164 break;
165 }
166 }
167 }
168 }
169 if state == PassageState::Tags {
170 warnings.push(Warning::PassageTagsMalformed(name.iter().collect()));
171 }
172 if ! tag.is_empty() {
173 tags.push(tag.iter().collect());
174 }
175 if meta.trim().len() == 0 {
176 meta = "{}";
177 }
178 start = a.end();
179 }
180 if ! name.is_empty() {
181 let name: String = name.iter().collect();
182 let name = name.trim().to_string();
183 let mut content = source[start..].to_string();
184 if content.starts_with("\\::") {
185 content.remove(0);
186 }
187 handle_passage(&mut warnings, &mut title, &mut story_meta, &mut passages, &name, &content, &tags, meta);
188 }
189 if title.is_empty() {
190 warnings.push(Warning::StoryTitleMissing);
191 }
192 return Ok((Story {
193 title,
194 passages,
195 meta: story_meta.unwrap_or(Map::new()),
196 }, warnings));
197}
198
199
200pub fn serialize_twee3(story: &Story) -> String {
202 let escape = |t: &String| {
203 t.replace("\\", "\\\\")
204 .replace("[", "\\[")
205 .replace("]", "\\]")
206 .replace("{", "\\{")
207 .replace("}", "\\}")
208 };
209
210 let mut res: Vec<char> = Vec::new();
211 res.extend(":: StoryTitle\n".chars());
212 res.extend(escape(&story.title).chars());
213
214 res.extend("\n\n:: StoryData\n".chars());
215 res.extend(serde_json::to_string_pretty(&story.meta).unwrap().chars());
216 res.extend("\n\n".chars());
217
218 for p in &story.passages {
219 res.extend("\n:: ".chars());
220 res.extend(escape(&p.name).chars());
221 if ! p.tags.is_empty() {
222 res.extend(" [".chars());
223 res.extend(p.tags.iter().map(escape).collect::<Vec<String>>().join(" ").chars());
224 res.push(']');
225 }
226 if ! p.meta.is_empty() {
227 res.extend(" {".chars());
228 res.extend(serde_json::to_string(&p.meta).unwrap().chars());
229 res.push('}');
230 }
231 res.push('\n');
232 if p.content.starts_with("::") {
233 res.push('\\');
234 }
235 res.extend(p.content.chars());
236 res.push('\n');
237 }
238 return res.into_iter().collect();
239}
240
241