1use crate::*;
2
3pub use xmltree::{Element, XMLNode, ParseError};
4
5pub use xmltree;
6
7fn search_storydata(e: &Element) -> Option<Element> {
8 if e.name == "tw-storydata" {
9 return Some(e.clone());
10 }
11 for c in &e.children {
12 if let Some(e) = c.as_element() {
13 if e.name == "tw-storydata" {
14 return Some(e.clone());
15 } else {
16 if let Some(e) = search_storydata(&e) {
17 return Some(e);
18 }
19 }
20 }
21 }
22 return None;
23}
24
25pub fn parse_archive(source: &str) -> Result<Vec<(Story, Vec<Warning>)>, Error> {
27 let e = Element::parse_all(source.as_bytes()).map_err(|e| Error::HTMLParseError(e))?;
28 return e.into_iter().map(|e| e.as_element().ok_or(Error::HTMLStoryDataNotFound).and_then(|e| parse_element(e))).collect();
29}
30
31pub fn parse_html(source: &str) -> Result<(Story, Vec<Warning>), Error> {
33 let e = Element::parse(source.as_bytes()).map_err(|e| Error::HTMLParseError(e))?;
34 let storydata = search_storydata(&e).ok_or(Error::HTMLStoryDataNotFound)?;
35 return parse_element(&storydata);
36}
37
38fn parse_element(storydata: &Element) -> Result<(Story, Vec<Warning>), Error> {
39 let mut warnings = vec![];
40 let mut passages: Vec<Passage> = vec![];
41 let mut tag_colors = Map::new();
42 let mut elements = storydata.children.iter().filter_map(|c| {
43 c.as_element()
44 }).collect::<Vec<&Element>>();
45 elements.sort_by(|a, b| {
46 let a = a.attributes.get("pid").and_then(|p| u32::from_str_radix(p, 10).ok()).unwrap_or(u32::MAX);
47 let b = b.attributes.get("pid").and_then(|p| u32::from_str_radix(p, 10).ok()).unwrap_or(u32::MAX);
48 a.cmp(&b)
49 });
50 for n in elements {
51 match n.name.as_str() {
52 "tw-passagedata" => {
53 let mut meta = Map::new();
54 for a in &n.attributes {
55 meta.insert(a.0.clone(), Value::String(a.1.clone()));
56 }
57 meta.remove("pid");
58 if let Some(name) = meta.remove("name") {
59 let tags = meta.remove("tags").and_then(|tags| {
60 Some(tags.as_str().unwrap().split_whitespace().map(|s| s.to_string()).collect())
61 }).unwrap_or(vec![]);
62 let p = Passage {
63 name: name.as_str().unwrap().to_string(),
64 tags,
65 meta,
66 content: n.get_text().unwrap().clone().to_string(),
67 };
68 passages.push(p);
69 }
70 },
71 "style" => {
72 if let Some(p) = passages.iter_mut().find(|p| p.name == "StoryStylesheet") {
73 if let Some(t) = n.get_text() {
74 p.content += "\n";
75 p.content += &t;
76 }
77 } else {
78 if let Some(t) = n.get_text() {
79 let p = Passage {
80 name: "StoryStylesheet".to_string(),
81 tags: vec!["stylesheet".to_string()],
82 meta: Map::new(),
83 content: t.clone().to_string(),
84 };
85 passages.push(p);
86 }
87 }
88 },
89 "script" => {
90 if let Some(p) = passages.iter_mut().find(|p| p.name == "StoryScript") {
91 if let Some(t) = n.get_text() {
92 p.content += "\n";
93 p.content += &t;
94 }
95 } else {
96 if let Some(t) = n.get_text() {
97 let p = Passage {
98 name: "StoryScript".to_string(),
99 tags: vec!["script".to_string()],
100 meta: Map::new(),
101 content: t.clone().to_string(),
102 };
103 passages.push(p);
104 }
105 }
106 },
107 "tw-tag" => {
108 if let (Some(name), Some(value)) = (n.attributes.get("name"), n.attributes.get("color")) {
109 tag_colors.insert(name.clone(), Value::String(value.clone()));
110 }
111 }
112 _ => {}
113 }
114 }
115
116
117 let mut meta = Map::new();
118 for a in &storydata.attributes {
119 meta.insert(a.0.clone(), Value::String(a.1.clone()));
120 }
121 let mut title = "".to_string();
122 meta.remove("hidden");
123 if let Some(t) = meta.remove("name") {
124 title = t.as_str().unwrap().to_string();
125 } else {
126 warnings.push(Warning::StoryTitleMissing);
127 }
128 if let Some(s) = meta.remove("startnode") {
129 if let Some(start) = s.as_str() {
130 let start = start.to_string();
131 if let Some(start) = storydata.children.iter().find(|c| c.as_element().and_then(|e| Some(e.attributes.get("pid") == Some(&start))).is_some()) {
132 if let Some(name) = start.as_element().and_then(|e| e.attributes.get("name")) {
133 meta.insert("start".to_string(), Value::String(name.clone()));
134 }
135 }
136 }
137 }
138 meta.insert("tag-colors".to_string(), Value::Object(tag_colors));
139
140 return Ok((Story {
141 title,
142 passages,
143 meta,
144 }, warnings));
145}
146
147pub fn serialize_html(story: &Story) -> Element {
149 let mut storydata = Element::new("tw-storydata");
150 storydata.attributes.insert("name".to_string(), story.title.clone());
151
152 let stylesheet = "stylesheet".to_string();
153 let script = "script".to_string();
154 let mut pid = 1;
155 for p in &story.passages {
156 let mut e;
157 if p.tags.contains(&stylesheet) {
158 if let Some(e) = storydata.children.iter_mut().find(|e| e.as_element().and_then(|e| Some(e.name == "style")) == Some(true)) {
159 let e = e.as_mut_element().unwrap();
160 e.children.push(XMLNode::Text("\n".to_string()));
161 e.children.push(XMLNode::Text(p.content.clone()));
162 continue;
163 }
164 e = Element::new("style");
165 e.attributes.insert("role".to_string(), "stylesheet".to_string());
166 e.attributes.insert("id".to_string(), "twine-user-stylesheet".to_string());
167 e.attributes.insert("type".to_string(), "text/twine-css".to_string());
168 e.children.push(XMLNode::Text(p.content.clone()));
169 } else {
170 if p.tags.contains(&script) {
171 if let Some(e) = storydata.children.iter_mut().find(|e| e.as_element().and_then(|e| Some(e.name == "script")) == Some(true)) {
172 let e = e.as_mut_element().unwrap();
173 e.children.push(XMLNode::Text("\n".to_string()));
174 e.children.push(XMLNode::Text(p.content.clone()));
175 continue;
176 }
177 e = Element::new("script");
178 e.attributes.insert("role".to_string(), "script".to_string());
179 e.attributes.insert("id".to_string(), "twine-user-script".to_string());
180 e.attributes.insert("type".to_string(), "text/twine-javascript".to_string());
181 e.children.push(XMLNode::Text(p.content.clone()));
182 } else {
183 e = Element::new("tw-passagedata");
184 e.attributes.insert("pid".to_string(), pid.to_string());
185 pid += 1;
186 e.attributes.insert("name".to_string(), p.name.clone());
187 e.attributes.insert("tags".to_string(), p.tags.join(" "));
188 for m in &p.meta {
189 if let Some(v) = m.1.as_str() {
190 e.attributes.insert(m.0.clone(), v.to_string());
191 }
192 }
193 let content = p.content.clone();
194 e.children.push(XMLNode::Text(content));
195 }
196 }
197 storydata.children.push(XMLNode::Element(e));
198 }
199
200
201 for m in &story.meta {
202 match m.0.as_str() {
203 "start" => {
204 if let Some(s) = m.1.as_str() {
205 let s = s.to_string();
206 if let Some(start) = storydata.children.iter().find(|c| c.as_element().and_then(|e| Some(e.attributes.get("name") == Some(&s))) == Some(true)) {
207 storydata.attributes.insert("startnode".to_string(), start.as_element().unwrap().attributes.get("pid").unwrap().clone());
208 }
209 }
210 },
211 "tag-colors" => {
212 if let Some(tags) = m.1.as_object() {
213 for t in tags {
214 if let Some(v) = t.1.as_str() {
215 let mut e = Element::new("tw-tag");
216 e.attributes.insert("name".to_string(), t.0.clone());
217 e.attributes.insert("color".to_string(), v.to_string());
218 storydata.children.insert(0, XMLNode::Element(e));
219 }
220 }
221 }
222 },
223 _ => {
224 if let Some(v) = &m.1.as_str() {
225 storydata.attributes.insert(m.0.clone(), v.to_string());
226 }
227 }
228 }
229 }
230 return storydata;
231}
232