Mercurial > repos > IBDev-IBBoard.WarFoundry.API
comparison api/Factories/Xml/WarFoundryXmlFactory.cs @ 52:64ef178c18aa
Re #10 - Refactor for readability
* Break WarFoundryXMLFactory out in to GameSystem, Race and Army factories
* Create factory utils classes with methods from WarFoundryXMLFactory for getting node lists etc
author | IBBoard <dev@ibboard.co.uk> |
---|---|
date | Mon, 30 Mar 2009 19:44:03 +0000 |
parents | b271a2252758 |
children | 3ea0ab04352b |
comparison
equal
deleted
inserted
replaced
51:b271a2252758 | 52:64ef178c18aa |
---|---|
24 /// The WarFoundryXmlFactory loads WarFoundry classes from the native "XML in a zip" file format. Files are validated using the schema for the file type, so structurally invalid files should be identified at initial load. | 24 /// The WarFoundryXmlFactory loads WarFoundry classes from the native "XML in a zip" file format. Files are validated using the schema for the file type, so structurally invalid files should be identified at initial load. |
25 /// </summary> | 25 /// </summary> |
26 public class WarFoundryXmlFactory : AbstractNativeWarFoundryFactory | 26 public class WarFoundryXmlFactory : AbstractNativeWarFoundryFactory |
27 { | 27 { |
28 private static WarFoundryXmlFactory factory; | 28 private static WarFoundryXmlFactory factory; |
29 private XmlReaderSettings settings; | 29 private WarFoundryXmlGameSystemFactory gameSystemFactory; |
30 private XmlNamespaceManager nsManager; | 30 private WarFoundryXmlRaceFactory raceFactory; |
31 private Dictionary<IWarFoundryObject, XmlDocument> extraData = new Dictionary<IWarFoundryObject, XmlDocument>(); | 31 private WarFoundryXmlArmyFactory armyFactory; |
32 | 32 |
33 public static AbstractNativeWarFoundryFactory GetFactory() | 33 public static AbstractNativeWarFoundryFactory GetFactory() |
34 { | 34 { |
35 if (factory == null) | 35 if (factory == null) |
36 { | 36 { |
40 return factory; | 40 return factory; |
41 } | 41 } |
42 | 42 |
43 private WarFoundryXmlFactory() : base() | 43 private WarFoundryXmlFactory() : base() |
44 { | 44 { |
45 //Hide constructor | 45 gameSystemFactory = new WarFoundryXmlGameSystemFactory(this); |
46 raceFactory = new WarFoundryXmlRaceFactory(this); | |
47 armyFactory = new WarFoundryXmlArmyFactory(); | |
46 } | 48 } |
47 | 49 |
48 protected override bool CheckCanFindArmyFileContent(ZipFile file) | 50 protected override bool CheckCanFindArmyFileContent(ZipFile file) |
49 { | 51 { |
50 return file.FindEntry("data.armyx", true) > -1; | 52 return file.FindEntry("data.armyx", true) > -1; |
66 } | 68 } |
67 | 69 |
68 protected override Army CreateArmyFromStream (ZipFile file, Stream dataStream) | 70 protected override Army CreateArmyFromStream (ZipFile file, Stream dataStream) |
69 { | 71 { |
70 XmlElement elem = GetRootElementFromStream(dataStream, WarFoundryXmlElementName.ARMY_ELEMENT); | 72 XmlElement elem = GetRootElementFromStream(dataStream, WarFoundryXmlElementName.ARMY_ELEMENT); |
71 return CreateArmyFromElement(file, elem); | 73 return armyFactory.CreateArmyFromElement(file, elem); |
72 } | 74 } |
73 | 75 |
74 private XmlElement GetRootElementFromStream(Stream stream, WarFoundryXmlElementName elementName) | 76 private XmlElement GetRootElementFromStream(Stream stream, WarFoundryXmlElementName elementName) |
75 { | 77 { |
76 XmlDocument doc = CreateXmlDocumentFromStream(stream); | 78 XmlDocument doc = WarFoundryXmlFactoryUtils.CreateXmlDocumentFromStream(stream); |
77 XmlElement elem = (XmlElement)doc.LastChild; | 79 XmlElement elem = (XmlElement)doc.LastChild; |
78 | 80 |
79 if (!elem.LocalName.Equals(elementName.Value)) | 81 if (!elem.LocalName.Equals(elementName.Value)) |
80 { | 82 { |
81 throw new InvalidFileException(String.Format("Root element of XML was not valid. Expected {0} but got {1}", elementName.Value, elem.Name)); | 83 throw new InvalidFileException(String.Format("Root element of XML was not valid. Expected {0} but got {1}", elementName.Value, elem.Name)); |
82 } | 84 } |
83 | 85 |
84 return elem; | 86 return elem; |
85 } | |
86 | |
87 private XmlDocument CreateXmlDocumentFromStream(Stream stream) | |
88 { | |
89 XmlDocument doc = new XmlDocument(); | |
90 XmlReader reader = XmlReader.Create(stream, GetReaderSettings()); | |
91 | |
92 try | |
93 { | |
94 doc.Load(reader); | |
95 } | |
96 //Don't catch XMLSchemaExceptions - let them get thrown out | |
97 finally | |
98 { | |
99 reader.Close(); | |
100 } | |
101 | |
102 return doc; | |
103 } | |
104 | |
105 /// <summary> | |
106 /// Lazy-getter for XML reader settings. May throw a <see cref="InvalidDataException"/> if there is a problem with the translation schema. | |
107 /// </summary> | |
108 /// <returns> | |
109 /// A <see cref="XmlReaderSettings"/> with the default values for validating the translation document against the translation schema | |
110 /// </returns> | |
111 private XmlReaderSettings GetReaderSettings() | |
112 { | |
113 if (settings == null) | |
114 { | |
115 settings = new XmlReaderSettings(); | |
116 settings.ValidationType = ValidationType.Schema; | |
117 settings.ValidationFlags = XmlSchemaValidationFlags.ReportValidationWarnings; | |
118 settings.ProhibitDtd = true; | |
119 settings.ValidationEventHandler+= new ValidationEventHandler(ValidationEventMethod); | |
120 XmlSchemaSet cache = new XmlSchemaSet(); | |
121 string path = IBBoard.Constants.ExecutablePath + "/dtds/"; | |
122 string nsBase = "http://ibboard.co.uk/warfoundry/"; | |
123 AddSchemaToCache(cache, nsBase + "core", path + "warfoundry-core.xsd"); | |
124 AddSchemaToCache(cache, nsBase + "cats", path + "warfoundry-cats.xsd"); | |
125 AddSchemaToCache(cache, nsBase + "race", path + "race.xsd"); | |
126 AddSchemaToCache(cache, nsBase + "system", path + "system.xsd"); | |
127 AddSchemaToCache(cache, nsBase + "army", path + "army.xsd"); | |
128 settings.Schemas.Add(cache); | |
129 } | |
130 | |
131 return settings; | |
132 } | |
133 | |
134 private void ValidationEventMethod(object sender, ValidationEventArgs e) | |
135 { | |
136 throw new InvalidDataException("Problem validating against schema for WarFoundry data: " + e.Exception.Message, e.Exception); | |
137 } | |
138 | |
139 private void AddSchemaToCache(XmlSchemaSet cache, string xmlNamespace, string schemaLocation) | |
140 { | |
141 try | |
142 { | |
143 cache.Add(xmlNamespace, schemaLocation); | |
144 } | |
145 catch (IOException ex) | |
146 { | |
147 LogNotifier.Warn(GetType(), "Problem reading schema: " + ex.Message, ex); | |
148 } | |
149 catch (XmlSchemaException ex) | |
150 { | |
151 LogNotifier.Warn(GetType(), "Problem validating schema for WarFoundry data: " + ex.Message, ex); | |
152 } | |
153 catch (XmlException ex) | |
154 { | |
155 LogNotifier.Warn(GetType(), "Problem reading data for schema: " + ex.Message, ex); | |
156 } | |
157 } | |
158 | |
159 private Army CreateArmyFromElement(ZipFile file, XmlElement elem) | |
160 { | |
161 string name = elem.GetAttribute("name"); | |
162 string systemID = elem.GetAttribute("gameSystem"); | |
163 GameSystem system = WarFoundryLoader.GetDefault().GetGameSystem(systemID); | |
164 string raceID = elem.GetAttribute("race"); | |
165 Race race = WarFoundryLoader.GetDefault().GetRace(system, raceID); | |
166 int points = XmlTools.GetIntValueFromAttribute(elem, "maxPoints"); | |
167 Army army = new Army(race, name, points, file); | |
168 //TODO: Complete loading of army | |
169 return army; | |
170 } | 87 } |
171 | 88 |
172 protected override Stream GetGameSystemDataStream (ZipFile file) | 89 protected override Stream GetGameSystemDataStream (ZipFile file) |
173 { | 90 { |
174 return file.GetInputStream(file.FindEntry("data.systemx", true)); | 91 return file.GetInputStream(file.FindEntry("data.systemx", true)); |
176 | 93 |
177 protected override GameSystem CreateGameSystemFromStream (ZipFile file, Stream dataStream) | 94 protected override GameSystem CreateGameSystemFromStream (ZipFile file, Stream dataStream) |
178 { | 95 { |
179 XmlElement elem = GetRootElementFromStream(dataStream, WarFoundryXmlElementName.SYSTEM_ELEMENT); | 96 XmlElement elem = GetRootElementFromStream(dataStream, WarFoundryXmlElementName.SYSTEM_ELEMENT); |
180 LogNotifier.Debug(GetType(), "Create GameSystem"); | 97 LogNotifier.Debug(GetType(), "Create GameSystem"); |
181 return CreateSystemFromElement(file, elem); | 98 return gameSystemFactory.CreateSystemFromElement(file, elem); |
182 } | |
183 | |
184 private GameSystem CreateSystemFromElement(ZipFile file, XmlElement elem) | |
185 { | |
186 string id = elem.GetAttribute("id"); | |
187 string name = elem.GetAttribute("name"); | |
188 GameSystem system = new GameSystem(id, name, this); | |
189 StoreExtraData(system, elem); | |
190 return system; | |
191 } | |
192 | |
193 private void StoreExtraData(WarFoundryStagedLoadingObject wfObject, XmlElement elem) | |
194 { | |
195 extraData[wfObject] = elem.OwnerDocument; | |
196 } | 99 } |
197 | 100 |
198 protected override Stream GetRaceDataStream (ZipFile file) | 101 protected override Stream GetRaceDataStream (ZipFile file) |
199 { | 102 { |
200 return file.GetInputStream(file.FindEntry("data.racex", true)); | 103 return file.GetInputStream(file.FindEntry("data.racex", true)); |
202 | 105 |
203 protected override Race CreateRaceFromStream (ZipFile file, Stream dataStream) | 106 protected override Race CreateRaceFromStream (ZipFile file, Stream dataStream) |
204 { | 107 { |
205 XmlElement elem = GetRootElementFromStream(dataStream, WarFoundryXmlElementName.RACE_ELEMENT); | 108 XmlElement elem = GetRootElementFromStream(dataStream, WarFoundryXmlElementName.RACE_ELEMENT); |
206 LogNotifier.Debug(GetType(), "Create Race"); | 109 LogNotifier.Debug(GetType(), "Create Race"); |
207 return CreateRaceFromElement(file, elem); | 110 return raceFactory.CreateRaceFromElement(file, elem); |
208 } | |
209 | |
210 private Race CreateRaceFromElement(ZipFile file, XmlElement elem) | |
211 { | |
212 string id = elem.GetAttribute("id"); | |
213 string subid = elem.GetAttribute("subid"); | |
214 string systemID = elem.GetAttribute("system"); | |
215 string name = elem.GetAttribute("name"); | |
216 Race race = new Race(id, subid, name, systemID, this); | |
217 StoreExtraData(race, elem); | |
218 return race; | |
219 } | |
220 | |
221 public XmlDocument GetExtraData(IWarFoundryObject obj) | |
222 { | |
223 XmlDocument extra = null; | |
224 extraData.TryGetValue(obj, out extra); | |
225 return extra; | |
226 } | |
227 | |
228 private XmlNamespaceManager GetNamespaceManager() | |
229 { | |
230 if (nsManager == null) | |
231 { | |
232 nsManager = new XmlNamespaceManager(new NameTable()); | |
233 nsManager.AddNamespace("core", "http://ibboard.co.uk/warfoundry/core"); | |
234 nsManager.AddNamespace("cat", "http://ibboard.co.uk/warfoundry/cats"); | |
235 nsManager.AddNamespace("race", "http://ibboard.co.uk/warfoundry/race"); | |
236 nsManager.AddNamespace("system", "http://ibboard.co.uk/warfoundry/system"); | |
237 nsManager.AddNamespace("army", "http://ibboard.co.uk/warfoundry/army"); | |
238 } | |
239 | |
240 return nsManager; | |
241 } | |
242 | |
243 private XmlNodeList SelectNodes(XmlNode element, string xpathQuery) | |
244 { | |
245 return element.SelectNodes(xpathQuery, GetNamespaceManager()); | |
246 } | |
247 | |
248 private XmlNode SelectSingleNode(XmlNode element, string xpathQuery) | |
249 { | |
250 return element.SelectSingleNode(xpathQuery, GetNamespaceManager()); | |
251 } | |
252 | |
253 private XmlElement SelectSingleElement(XmlNode element, string xpathQuery) | |
254 { | |
255 XmlNode node = SelectSingleNode(element, xpathQuery); | |
256 return (node is XmlElement) ? (XmlElement) node : null; | |
257 } | 111 } |
258 | 112 |
259 public override void CompleteLoading(IWarFoundryStagedLoadObject obj) | 113 public override void CompleteLoading(IWarFoundryStagedLoadObject obj) |
260 { | 114 { |
261 LogNotifier.DebugFormat(GetType(), "Complete loading of {0} with ID {1}", obj.GetType().Name, obj.ID); | 115 LogNotifier.DebugFormat(GetType(), "Complete loading of {0} with ID {1}", obj.GetType().Name, obj.ID); |
262 | 116 |
263 if (obj is GameSystem) | 117 if (obj is GameSystem) |
264 { | 118 { |
265 CompleteLoading((GameSystem)obj); | 119 gameSystemFactory.CompleteLoading((GameSystem)obj); |
266 } | 120 } |
267 else if (obj is Race) | 121 else if (obj is Race) |
268 { | 122 { |
269 CompleteLoading((Race)obj); | 123 raceFactory.CompleteLoading((Race)obj); |
270 } | 124 } |
271 } | |
272 | |
273 public void CompleteLoading(GameSystem system) | |
274 { | |
275 if (!CanCompleteLoading(system)) | |
276 { | |
277 return; | |
278 } | |
279 | |
280 system.SetAsLoading(); | |
281 XmlDocument extraData = GetExtraData(system); | |
282 LoadCategoriesForSystem(system, extraData); | |
283 XmlElement statsElem = SelectSingleElement(extraData, "/system:system/system:sysStatsList"); | |
284 string defaultStatsID = statsElem.GetAttribute("defaultStats"); | |
285 LoadSystemStatsForSystem(system, extraData); | |
286 system.StandardSystemStatsID = defaultStatsID; | |
287 XmlElement systemElement = SelectSingleElement(extraData, "/system:system"); | |
288 system.WarnOnError = XmlTools.GetBoolValueFromAttribute(systemElement, "warn"); | |
289 system.AllowAllies = XmlTools.GetBoolValueFromAttribute(systemElement, "allowAllies"); | |
290 LogNotifier.DebugFormat(GetType(), "Completed loading of GameSystem with ID {0}", system.ID); | |
291 LogNotifier.DebugFormat(GetType(), "GameSystem with ID {0} default stats: {1}", system.ID, system.StandardSystemStatsID); | |
292 system.SetAsFullyLoaded(); | |
293 } | |
294 | |
295 private bool CanCompleteLoading(IWarFoundryStagedLoadObject obj) | |
296 { | |
297 bool canLoad = true; | |
298 | |
299 if (obj.IsFullyLoaded) | |
300 { | |
301 LogNotifier.DebugFormat(GetType(), "Object of type {0} with ID {1} is already fully loaded", obj.GetType().Name, obj.ID); | |
302 canLoad = false; | |
303 } | |
304 else if (obj.IsLoading) | |
305 { | |
306 LogNotifier.WarnFormat(GetType(), "Object of type {0} with ID {1} is already being loaded", obj.GetType().Name, obj.ID); | |
307 canLoad = false; | |
308 } | |
309 | |
310 return canLoad; | |
311 } | |
312 | |
313 private void LoadCategoriesForSystem(GameSystem system, XmlNode elem) | |
314 { | |
315 foreach (XmlElement cat in SelectNodes(elem, "/system:system/system:categories/cat:cat")) | |
316 { | |
317 system.AddCategory(CreateCategoryFromElement(cat)); | |
318 } | |
319 } | |
320 | |
321 private Category CreateCategoryFromElement(XmlElement elem) | |
322 { | |
323 string id = elem.GetAttribute("id"); | |
324 string name = elem.GetAttribute("name"); | |
325 Category cat = new Category(id, name); | |
326 cat.MaximumPercentage = XmlTools.GetIntValueFromAttribute(elem, "maxPercentage"); | |
327 cat.MinimumPercentage = XmlTools.GetIntValueFromAttribute(elem, "minPercentage"); | |
328 cat.MaximumPoints = XmlTools.GetIntValueFromAttribute(elem, "maxPoints"); | |
329 cat.MinimumPoints = XmlTools.GetIntValueFromAttribute(elem, "minPoints"); | |
330 return cat; | |
331 } | |
332 | |
333 private void LoadSystemStatsForSystem(GameSystem system, XmlNode elem) | |
334 { | |
335 foreach (XmlElement stats in SelectNodes(elem, "/system:system/system:sysStatsList/system:sysStats")) | |
336 { | |
337 SystemStats sysStats = CreateSystemStatsFromElement(stats); | |
338 system.AddSystemStats(sysStats); | |
339 } | |
340 } | |
341 | |
342 private SystemStats CreateSystemStatsFromElement(XmlElement elem) | |
343 { | |
344 List<StatSlot> slots = new List<StatSlot>(); | |
345 string id = elem.GetAttribute("id"); | |
346 | |
347 foreach (XmlElement slot in elem.ChildNodes) | |
348 { | |
349 StatSlot statSlot = new StatSlot(slot.GetAttribute("name")); | |
350 slots.Add(statSlot); | |
351 } | |
352 | |
353 return new SystemStats(id, slots.ToArray()); | |
354 } | |
355 | |
356 public void CompleteLoading(Race race) | |
357 { | |
358 if (!CanCompleteLoading(race)) | |
359 { | |
360 return; | |
361 } | |
362 | |
363 race.SetAsLoading(); | |
364 XmlDocument extraData = GetExtraData(race); | |
365 | |
366 foreach (XmlElement node in SelectNodes(extraData, "/race:race/race:units/race:unit")) | |
367 { | |
368 UnitType type = CreateUnitTypeFromElement(node, race, race.GameSystem); | |
369 race.AddUnitType(type); | |
370 } | |
371 | |
372 foreach (XmlElement node in SelectNodes(extraData, "/race:race/race:categories/cat:cat")) | |
373 { | |
374 race.AddCategory(CreateCategoryFromElement(node)); | |
375 } | |
376 | |
377 foreach (XmlElement node in SelectNodes(extraData, "/race:race/race:equipment/cat:equipmentItem")) | |
378 { | |
379 EquipmentItem item = CreateEquipmentItemFromElement(node, race); | |
380 race.AddEquipmentItem(item); | |
381 } | |
382 | |
383 foreach (XmlElement node in SelectNodes(extraData, "/race:race/race:abilities/cat:ability")) | |
384 { | |
385 Ability ability = CreateAbilityFromElement(node, race); | |
386 race.AddAbility(ability); | |
387 } | |
388 | |
389 race.SetAsFullyLoaded(); | |
390 LogNotifier.DebugFormat(GetType(), "Completed loading of Race with ID {0}", race.ID); | |
391 } | |
392 | |
393 private UnitType CreateUnitTypeFromElement(XmlElement elem, Race parentRace, GameSystem system) | |
394 { | |
395 string id = elem.GetAttribute("id"); | |
396 string name = elem.GetAttribute("typeName"); | |
397 UnitType type = new UnitType(id, name, parentRace); | |
398 type.MaxNumber = XmlTools.GetIntValueFromAttribute(elem, "maxNum"); | |
399 type.MinNumber = XmlTools.GetIntValueFromAttribute(elem, "minNum"); | |
400 type.MaxSize = XmlTools.GetIntValueFromAttribute(elem, "maxSize"); | |
401 type.MinSize = XmlTools.GetIntValueFromAttribute(elem, "minSize"); | |
402 type.BaseSize = XmlTools.GetIntValueFromAttribute(elem, "baseSize"); | |
403 type.CostPerTrooper = XmlTools.GetIntValueFromAttribute(elem, "points"); | |
404 type.BaseUnitCost = XmlTools.GetIntValueFromAttribute(elem, "unitPoints"); | |
405 string mainCatID = elem.GetAttribute("cat"); | |
406 Category cat = parentRace.GetCategory(mainCatID); | |
407 | |
408 if (cat == null) | |
409 { | |
410 throw new InvalidDataException(String.Format("Attribute 'cat' of UnitType {0} (value: {1}) did not reference a valid category", id, mainCatID)); | |
411 } | |
412 | |
413 type.MainCategory = cat; | |
414 XmlElement statsElement = SelectSingleElement(elem, "/race:race/race:units/race:unit/race:stats"); | |
415 type.UnitStats = ParseUnitStats(statsElement, system); | |
416 //TODO: Add unit requirements | |
417 LogNotifier.Debug(GetType(), "Loaded "+type.Name); | |
418 return type; | |
419 } | |
420 | |
421 private Stats ParseUnitStats(XmlElement elem, GameSystem system) | |
422 { | |
423 List<Stat> statsList = new List<Stat>(); | |
424 String statsID = elem.GetAttribute("statSet"); | |
425 SystemStats statsSet; | |
426 | |
427 if (statsID == "") | |
428 { | |
429 statsSet = system.StandardSystemStats; | |
430 } | |
431 else | |
432 { | |
433 statsSet = system.GetSystemStatsForID(statsID); | |
434 } | |
435 | |
436 Stats stats = new Stats(statsSet); | |
437 | |
438 foreach (XmlElement stat in elem.ChildNodes) | |
439 { | |
440 String statID = stat.GetAttribute("name"); | |
441 StatSlot slot = statsSet[statID]; | |
442 | |
443 if (slot!=null) | |
444 { | |
445 statsList.Add(new Stat(slot, stat.InnerText)); | |
446 } | |
447 else | |
448 { | |
449 throw new InvalidFileException("The stat "+statID+" was not found in stats set "+statsID); | |
450 } | |
451 } | |
452 | |
453 stats.SetStats(statsList); | |
454 | |
455 return stats; | |
456 } | |
457 | |
458 private EquipmentItem CreateEquipmentItemFromElement(XmlElement elem, Race race) | |
459 { | |
460 string id = elem.GetAttribute("id"); | |
461 string name = elem.GetAttribute("name"); | |
462 double cost = 0, min = 0, max = 0; | |
463 ArmourType armourType; | |
464 | |
465 try | |
466 { | |
467 cost = XmlTools.GetDoubleValueFromAttribute(elem, "cost"); | |
468 } | |
469 catch(FormatException ex) | |
470 { | |
471 throw new InvalidFileException("Attribute 'cost' of equipment item "+id+" was not a valid number", ex); | |
472 } | |
473 | |
474 try | |
475 { | |
476 armourType = (ArmourType)Enum.Parse(typeof(ArmourType), elem.GetAttribute("armourType")); | |
477 } | |
478 catch(ArgumentException ex) | |
479 { | |
480 throw new InvalidFileException("Attribute 'armourType' of equipment "+id+" was not a valid value from the enumeration", ex); | |
481 } | |
482 | |
483 //TODO: Parse equipment stats if there are any | |
484 | |
485 return new EquipmentItem(id, name, cost, min, max, armourType, race); | |
486 } | |
487 | |
488 private Ability CreateAbilityFromElement(XmlElement elem, Race race) | |
489 { | |
490 string id = elem.GetAttribute("id"); | |
491 string name = elem.GetAttribute("name"); | |
492 Ability ability = new Ability(id, name); | |
493 XmlNode node = elem.SelectSingleNode("description", GetNamespaceManager()); | |
494 ability.Description = (node == null) ? "" : node.InnerText; | |
495 return ability; | |
496 } | 125 } |
497 } | 126 } |
498 } | 127 } |