rpfm_lib/files/asset_variation_db/
xml.rs1use crate::xml::{XmlElement, parse_elements};
17
18use super::*;
19
20const ENVIRONMENT_MAP_CATEGORIES: [&str; 8] = ["map", "climate", "season", "climate_change_index", "precipitation", "time_of_day", "battle_types", "snow_attrition"];
22
23const ENVIRONMENT_KEY: &str = "environment";
25
26impl AssetVariationDb {
31
32 pub fn to_xml(&self) -> String {
34 let mut xml = String::from("<warscape_asset_variation_db serialise_version='1'>\r\n\t<meta_data_entries>\r\n");
35 for category in &self.categories {
36 xml.push_str(&format!("\t\t<meta_data_entry serialise_version='1' name='{}'>\r\n\t\t\t<values", escape(&category.name)));
37 for value in &category.values {
38 xml.push_str(&format!(" value='{}'", escape(value)));
39 }
40 xml.push_str("/>\r\n\t\t</meta_data_entry>\r\n");
41 }
42
43 xml.push_str("\t</meta_data_entries>\r\n\t<entries>\r\n");
44 for entry in &self.entries {
45 xml.push_str(&format!("\t\t<entry key='{}'>\r\n\t\t\t<value serialise_version='1'>\r\n\t\t\t\t<variations>\r\n", escape(&entry.key)));
46 for variation in &entry.variations {
47 xml.push_str(&format!("\t\t\t\t\t<variation serialise_version='1' filename='{}' flags='{}'/>\r\n", escape(&variation.filename), variation.flags));
48 }
49 xml.push_str("\t\t\t\t</variations>\r\n\t\t\t</value>\r\n\t\t</entry>\r\n");
50 }
51
52 xml.push_str("\t</entries>\r\n</warscape_asset_variation_db>\r\n");
53 xml
54 }
55
56 pub fn from_xml(xml: &str) -> Result<Self> {
62 let (elements, roots) = parse_elements(xml).map_err(xml_error)?;
63 let [root] = roots.as_slice() else { return Err(xml_error("there must be one root element".to_owned())) };
64 let root = &elements[*root];
65 if root.name != "warscape_asset_variation_db" {
66 return Err(xml_error("the root element is not warscape_asset_variation_db".to_owned()));
67 }
68
69 check_version(root)?;
70
71 let mut db = Self {
72 serialise_version: 1,
73 ..Default::default()
74 };
75
76 for &index in &child(&elements, root, "meta_data_entries")?.children {
77 let element = &elements[index];
78 if element.name != "meta_data_entry" {
79 return Err(xml_error(format!("unexpected <{}> in meta_data_entries", element.name)));
80 }
81
82 check_version(element)?;
83 let values = child(&elements, element, "values")?;
84 let values = values.attributes.iter().map(|(name, value)| if name == "value" {
85 Ok(value.to_owned())
86 } else {
87 Err(xml_error(format!("<values> attribute '{name}' is not 'value'")))
88 }).collect::<Result<_>>()?;
89
90 db.categories.push(VariationCategory {
91 name: attribute(element, "name")?.to_owned(),
92 values,
93 });
94 }
95
96 for &index in &child(&elements, root, "entries")?.children {
97 let element = &elements[index];
98 if element.name != "entry" {
99 return Err(xml_error(format!("unexpected <{}> in entries", element.name)));
100 }
101
102 let value = child(&elements, element, "value")?;
103 check_version(value)?;
104
105 let mut entry = VariationEntry {
106 key: attribute(element, "key")?.to_owned(),
107 variations: vec![],
108 };
109
110 for &index in &child(&elements, value, "variations")?.children {
111 let variation = &elements[index];
112 if variation.name != "variation" {
113 return Err(xml_error(format!("unexpected <{}> in variations", variation.name)));
114 }
115
116 check_version(variation)?;
117 let filename = attribute(variation, "filename")?.to_owned();
118 let flags = attribute(variation, "flags")?;
119
120 let flags = flags.parse::<u32>().ok().filter(|_| flags.bytes().all(|byte| byte.is_ascii_digit()))
122 .ok_or_else(|| xml_error(format!("flags '{flags}' of {filename} is not an unsigned 32-bit number")))?;
123
124 entry.variations.push(Variation { filename, flags });
125 }
126
127 db.entries.push(entry);
128 }
129
130 Ok(db)
131 }
132
133 pub fn from_environment_map(xml: &str) -> Result<Self> {
143 let (elements, _) = parse_elements(xml).map_err(xml_error)?;
144 let files = elements.iter().filter(|element| element.name == "environment_file").collect::<Vec<_>>();
145 if files.is_empty() {
146 return Err(xml_error("no <environment_file> entries".to_owned()));
147 }
148
149 let lists = files.iter().map(|file| {
150 ENVIRONMENT_MAP_CATEGORIES.iter().map(|category| {
151 file.attributes.iter()
152 .find(|(name, _)| name == category)
153 .map(|(_, list)| list.split(',').map(|value| value.trim_matches([' ', '\t'])).filter(|value| !value.is_empty()).collect::<Vec<_>>())
154 .unwrap_or_default()
155 }).collect::<Vec<_>>()
156 }).collect::<Vec<_>>();
157
158 let mut db = Self {
159 serialise_version: 1,
160 ..Default::default()
161 };
162
163 for (index, name) in ENVIRONMENT_MAP_CATEGORIES.iter().enumerate() {
164 let mut values = lists.iter().flat_map(|file| file[index].iter().map(|value| value.to_string())).collect::<Vec<_>>();
165 values.sort();
166 values.dedup();
167 db.categories.push(VariationCategory { name: name.to_string(), values });
168 }
169
170 if db.value_count() > FLAG_BITS {
171 return Err(xml_error(format!("more than {FLAG_BITS} category values, the flags would collide")));
172 }
173
174 let mut entry = VariationEntry {
175 key: ENVIRONMENT_KEY.to_owned(),
176 variations: Vec::with_capacity(files.len()),
177 };
178
179 for (file, lists) in files.iter().zip(&lists) {
180 let allowed = ENVIRONMENT_MAP_CATEGORIES.iter().zip(lists)
181 .filter(|(_, list)| !list.is_empty())
182 .map(|(category, list)| (category.to_string(), list.iter().map(|value| value.to_string()).collect()))
183 .collect();
184
185 entry.variations.push(Variation {
186 filename: file.attributes.iter().find(|(name, _)| name == "filename").map(|(_, value)| value.to_owned()).unwrap_or_default(),
187 flags: db.flags_for(&allowed),
188 });
189 }
190
191 db.entries.push(entry);
192 Ok(db)
193 }
194}
195
196fn child<'a>(elements: &'a [XmlElement], parent: &XmlElement, name: &str) -> Result<&'a XmlElement> {
198 let mut found = parent.children.iter().map(|&index| &elements[index]).filter(|element| element.name == name);
199 match (found.next(), found.next()) {
200 (Some(element), None) => Ok(element),
201 (Some(_), Some(_)) => Err(xml_error(format!("more than one <{name}> in <{}>", parent.name))),
202 (None, _) => Err(xml_error(format!("missing <{name}> in <{}>", parent.name))),
203 }
204}
205
206fn attribute<'a>(element: &'a XmlElement, name: &str) -> Result<&'a str> {
208 element.attributes.iter()
209 .find(|(attribute, _)| attribute == name)
210 .map(|(_, value)| value.as_str())
211 .ok_or_else(|| xml_error(format!("<{}> has no {name} attribute", element.name)))
212}
213
214fn check_version(element: &XmlElement) -> Result<()> {
216 if attribute(element, "serialise_version")? != "1" {
217 return Err(xml_error(format!("<{}> serialise_version is not 1", element.name)));
218 }
219
220 Ok(())
221}
222
223fn escape(value: &str) -> String {
225 value.replace('&', "&").replace('<', "<").replace('\'', "'")
226}
227
228fn xml_error(reason: String) -> RLibError {
230 RLibError::DecodingAssetVariationDbXml(reason)
231}