comparison api/Factories/Xml/WarFoundryXmlFactory.cs @ 0:520818033bb6

Initial commit of WarFoundry code
author IBBoard <dev@ibboard.co.uk>
date Fri, 19 Dec 2008 15:57:51 +0000
parents
children 150a5669cd7b
comparison
equal deleted inserted replaced
-1:000000000000 0:520818033bb6
1 // WarFoundryXmlFactory.cs
2 //
3 // Copyright (C) 2007 IBBoard
4 //
5 // This library is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU Lesser General Public
7 // License version 2.1 of the License as published by the Free
8 // Software Foundation.
9 //
10 // This library is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 // Lesser General Public License for more details.
14 //
15 // You should have received a copy of the GNU Lesser General Public
16 // License along with this library; if not, write to the Free Software
17 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 //
19 //
20
21 using System;
22 using System.IO;
23 using System.Xml;
24 using System.Xml.Schema;
25 using System.Collections.Generic;
26 using System.Text;
27 using IBBoard;
28 using IBBoard.IO;
29 using IBBoard.Lang;
30 using IBBoard.Logging;
31 using IBBoard.Xml;
32 using IBBoard.WarFoundry.API.Requirements;
33 using IBBoard.WarFoundry.API.Objects;
34 using ICSharpCode.SharpZipLib.Zip;
35
36 namespace IBBoard.WarFoundry.API.Factories.Xml
37 {
38 /// <summary>
39 /// Summary description for WarFoundryXmlFactory.
40 /// </summary>
41 public class WarFoundryXmlFactory : AbstractNativeWarFoundryFactory
42 {
43 private Dictionary<IWarFoundryObject, XmlDocument> extraData = new Dictionary<IWarFoundryObject, XmlDocument>();
44 private XmlResolver xmlResolver;
45
46 public static AbstractNativeWarFoundryFactory CreateFactory()
47 {
48 return new WarFoundryXmlFactory();
49 }
50
51 protected WarFoundryXmlFactory() : base()
52 {
53 xmlResolver = new IBBXmlResolver(Constants.ExecutablePath);
54 }
55
56 protected override bool CheckCanFindArmyFileContent(ZipFile file)
57 {
58 return file.FindEntry("data.armyx", true) > -1;
59 }
60
61 protected override bool CheckCanFindSystemFileContent(ZipFile file)
62 {
63 return file.FindEntry("data.systemx", true) > -1;
64 }
65
66 protected override bool CheckCanFindRaceFileContent(ZipFile file)
67 {
68 return file.FindEntry("data.racex", true) > -1;
69 }
70
71 protected XmlElement GetRootElementFromStream(Stream stream, WarFoundryXmlElementName elementName)
72 {
73 XmlDocument doc = CreateXmlDocumentFromStream(stream);
74 XmlElement elem = (XmlElement)doc.LastChild;
75
76 if (!elem.Name.Equals(elementName.Value))
77 {
78 throw new InvalidFileException(String.Format("Root element of XML was not valid. Expected {0} but got {1}", elementName.Value, elem.Name));
79 }
80
81 return elem;
82 }
83
84 protected override Stream GetArmyDataStream(ZipFile file)
85 {
86 return file.GetInputStream(file.FindEntry("data.armyx", true));
87 }
88
89 protected override Army CreateArmyFromStream (ZipFile file, Stream dataStream)
90 {
91 XmlElement elem = GetRootElementFromStream(dataStream, WarFoundryXmlElementName.ARMY_ELEMENT);
92 return CreateArmyFromElement(file, elem);
93 }
94
95 private Army CreateArmyFromElement(ZipFile file, XmlElement elem)
96 {
97 string name = elem.GetAttribute("name");
98 string systemID = elem.GetAttribute("gameSystem");
99 GameSystem system = WarFoundryLoader.GetDefault().GetGameSystem(systemID);
100 string raceID = elem.GetAttribute("race");
101 Race race = WarFoundryLoader.GetDefault().GetRace(system, raceID);
102 string pointsString = elem.GetAttribute("maxPoints");
103 int points = 0;
104
105 try
106 {
107 points = int.Parse(pointsString);
108 }
109 catch(FormatException)
110 {
111 throw new FormatException("Attribute 'maxPoints' of army '"+name+"' was not a valid number");
112 }
113
114 Army army = new Army(race, name, points, file);//, this);
115 extraData[army] = elem.OwnerDocument;
116 return army;
117 }
118
119 protected override Stream GetGameSystemDataStream (ZipFile file)
120 {
121 return file.GetInputStream(file.FindEntry("data.systemx", true));
122 }
123
124 protected override GameSystem CreateGameSystemFromStream (ZipFile file, Stream dataStream)
125 {
126 XmlElement elem = GetRootElementFromStream(dataStream, WarFoundryXmlElementName.SYSTEM_ELEMENT);
127 LogNotifier.Debug(GetType(), "Create GameSystem");
128 return CreateSystemFromElement(file, elem);
129 }
130
131 private GameSystem CreateSystemFromElement(ZipFile file, XmlElement elem)
132 {
133 string id = elem.GetAttribute("id");
134 string name = elem.GetAttribute("name");
135 GameSystem system = new StagedLoadingGameSystem(id, name, this);
136 //system.SourceZipFile = file.;
137 extraData[system] = elem.OwnerDocument;
138 return system;
139 }
140
141 protected override Stream GetRaceDataStream (ZipFile file)
142 {
143 return file.GetInputStream(file.FindEntry("data.racex", true));
144 }
145
146 protected override Race CreateRaceFromStream (ZipFile file, Stream dataStream)
147 {
148 XmlElement elem = GetRootElementFromStream(dataStream, WarFoundryXmlElementName.RACE_ELEMENT);
149 LogNotifier.Debug(GetType(), "Create Race");
150 return CreateRaceFromElement(file, elem);
151 }
152
153 private Race CreateRaceFromElement(ZipFile file, XmlElement elem)
154 {
155 string id = elem.GetAttribute("id");
156 string subid = elem.GetAttribute("subid");
157 string systemID = elem.GetAttribute("system");
158 string name = elem.GetAttribute("name");
159 Race race = new StagedLoadingRace(id, subid, name, systemID, this);
160 //race.SourceZipFile = file; //TODO reference source file
161 extraData[race] = elem.OwnerDocument;
162 return race;
163 }
164
165 /*public WarFoundryObject CreateObjectFromStream(ZipFile file, Stream stream)
166 {
167 try
168 {
169 WarFoundryObject obj = LoadFileObjectFromElement(file, elem);
170
171 if (obj != null)
172 {
173 extraData[obj] = doc;
174 return obj;
175 }
176 else
177 {
178 throw new InvalidFileException(String.Format(Translation.GetTranslation("ErrorInvalidXmlFile", "XML file '{0}' was not a loadable XML file. Please ensure it is a valid and supported WarFoundry XML file."), file.Name));
179 }
180 }
181 catch (XmlSchemaException ex)
182 {
183 throw new InvalidFileException(String.Format(Translation.GetTranslation("ErrorInvalidXmlFile", "Failed to parse invalid XML data file in {0}. Error at line {1} position {2}: {3}"), file.Name, ex.LineNumber, ex.LinePosition, ex.Message.Substring(0, ex.Message.IndexOf(".")+1)), ex);
184 }
185 catch (InvalidFileException ex)
186 {
187 throw new InvalidFileException(String.Format(Translation.GetTranslation("ErrorInvalidNamedXmlFile", "XML data file in '{0}' was not a valid XML file. It should contain three child nodes - XML definition, DocType and root node."), file.Name), ex);
188 }
189 }
190
191 private WarFoundryObject LoadFileObjectFromElement(ZipFile file, XmlElement elem)
192 {
193 WarFoundryObject obj = null;
194
195 if (elem.Name.Equals(WarFoundryXmlElementName.SYSTEM_ELEMENT.Value))
196 {
197 logger.Debug("Create GameSystem");
198 obj = CreateSystemFromElement(file, elem);
199 }
200 else if (elem.Name.Equals(WarFoundryXmlElementName.RACE_ELEMENT.Value))
201 {
202 logger.Debug("Create Race");
203 obj = CreateRaceFromElement(file, elem);
204 }
205
206 return obj;
207 }*/
208
209 public override void CompleteLoading(IWarFoundryStagedLoadObject obj)
210 {
211 LogNotifier.DebugFormat(GetType(), "Complete loading of {0} with ID {1}", obj.GetType().Name, obj.ID);
212
213 if (!obj.IsFullyLoaded)
214 {
215 XmlDocument extra = extraData[obj];
216
217 if (obj is GameSystem)
218 {
219 GameSystem system = (GameSystem)obj;
220 XmlNode elem = extra.LastChild;
221
222 XmlNode catsElem = elem.FirstChild;
223 Category[] cats;
224 List<Category> catList = new List<Category>();
225 WarFoundryObject tempObj;
226
227 foreach (XmlElement cat in catsElem.ChildNodes)
228 {
229 tempObj = CreateObjectFromElement(cat);
230
231 if (tempObj is Category)
232 {
233 catList.Add((Category)tempObj);
234 }
235 else
236 {
237 LogNotifier.WarnFormat(GetType(), "Object for string {0} was not of type Category", cat.OuterXml);
238 }
239 }
240
241 cats = catList.ToArray();
242 LogNotifier.DebugFormat(GetType(), "Found {0} categories", cats.Length);
243
244 XmlElement statsElem = (XmlElement)catsElem.NextSibling;
245 Dictionary<string, SystemStats> sysStats = CreateSystemStatsSetFromElement(statsElem);
246 string defaultStatsID = statsElem.GetAttribute("defaultStats");
247
248 LogNotifier.DebugFormat(GetType(), "Complete loading of {0}", system.Name);
249 system.Categories = cats;
250 system.SystemStats = new SystemStatsSet(sysStats);
251 system.StandardSystemStatsID = defaultStatsID;
252 }
253 else if (obj is Race)
254 {
255 Race race = (Race)obj;
256 XmlNode elem = extra.LastChild;
257 XmlNode colNode = elem.FirstChild;
258
259 Dictionary<string, UnitType> unitTypes = new Dictionary<string, UnitType>();
260
261 foreach (XmlElement node in colNode.ChildNodes)
262 {
263 UnitType type = CreateUnitTypeFromElement(node, race, race.GameSystem);
264 unitTypes.Add(type.ID, type);
265 }
266
267 colNode = colNode.NextSibling;
268 List<Category> catOverrides = new List<Category>();
269
270 if (colNode!=null && colNode.Name == WarFoundryXmlElementName.CATEGORIES_ELEMENT.Value)
271 {
272 foreach (XmlElement node in colNode.ChildNodes)
273 {
274 catOverrides.Add(CreateCategoryFromElement(node));
275 }
276
277 colNode = colNode.NextSibling;
278 }
279
280 Dictionary<string, EquipmentItem> raceEquipment = new Dictionary<string, EquipmentItem>();
281
282 if (colNode!=null && colNode.Name == WarFoundryXmlElementName.RACE_EQUIPMENT_ITEMS_ELEMENT.Value)
283 {
284 foreach (XmlElement node in colNode.ChildNodes)
285 {
286 EquipmentItem item = CreateEquipmentItemFromElement(node, race);
287 raceEquipment.Add(item.ID, item);
288 }
289 }
290
291 Dictionary<string, Ability> raceAbilities = new Dictionary<string, Ability>();
292 //TODO: Load abilities
293
294 LogNotifier.DebugFormat(GetType(), "Complete loading of {0}", race.Name);
295 race.Categories = catOverrides.ToArray();
296 race.SetUnitTypes(unitTypes);
297 race.SetEquipmentItems(raceEquipment);
298 race.SetAbilities(raceAbilities);
299 }
300 }
301 else
302 {
303 LogNotifier.Debug(GetType(), "Object is already fully loaded");
304 }
305 }
306
307 protected XmlDocument CreateXmlDocumentFromStream(Stream stream)
308 {
309 XmlDocument doc = new XmlDocument();
310 XmlReaderSettings settings = new XmlReaderSettings();
311 settings.XmlResolver = xmlResolver;
312 settings.ValidationType = ValidationType.DTD;
313 settings.ProhibitDtd = false;
314 settings.ValidationEventHandler+= new ValidationEventHandler(ValidationEventMethod);
315 XmlReader reader = XmlReader.Create(stream, settings);
316
317 try
318 {
319 doc.Load(reader);
320 }
321 //Don't catch XMLSchemaExceptions - let them get thrown out
322 finally
323 {
324 reader.Close();
325 }
326
327 if (doc.ChildNodes.Count!=3)
328 {
329 throw new InvalidFileException(Translation.GetTranslation("ErrorInvalidXmlFile", "XML file was not a valid XML file. It should contain three child nodes - XML definition, DocType and root node."));
330 }
331
332 return doc;
333 }
334
335 protected XmlDocument CreateXmlDocumentFromString(string xmlString)
336 {
337 XmlReaderSettings settings = new XmlReaderSettings();
338 settings.XmlResolver = xmlResolver;
339 settings.ValidationType = ValidationType.DTD;
340 settings.ProhibitDtd = false;
341 settings.ValidationEventHandler+= new ValidationEventHandler(ValidationEventMethod);
342 XmlReader reader = XmlReader.Create(new MemoryStream(Encoding.UTF8.GetBytes(xmlString)), settings);
343 XmlDocument doc = new XmlDocument();
344
345 try
346 {
347 doc.Load(reader);
348 }
349 catch(XmlSchemaException ex)
350 {
351 throw new InvalidFileException(Translation.GetTranslation("ErrorInvalidXmlString", "Failed to parse invalid XML string"), ex);
352 }
353 finally
354 {
355 //We might not need to make sure the memory stream is closed, but do it just in case
356 reader.Close();
357 }
358
359 if (doc.ChildNodes.Count!=3)
360 {
361 throw new InvalidFileException(String.Format(Translation.GetTranslation("ErrorInvalidXmlFile", "XML string was not a valid XML file. It should contain three child nodes - XML definition, DocType and root node.")));
362 }
363
364 return doc;
365 }
366
367 private WarFoundryObject CreateObjectFromElement(XmlElement elem)
368 {
369 WarFoundryObject obj = null;
370 LogNotifier.DebugFormat(GetType(), "Create object for <{0}>", elem.Name);
371
372 if (elem.Name.Equals(WarFoundryXmlElementName.CATEGORY_ELEMENT.Value))
373 {
374 LogNotifier.Debug(GetType(), "Create Category");
375 obj = CreateCategoryFromElement(elem);
376 }
377 else
378 {
379 LogNotifier.Debug(GetType(), "No match");
380 }
381
382 return obj;
383 }
384
385 private Category CreateCategoryFromElement(XmlElement elem)
386 {
387 string id = elem.GetAttribute("id");
388 string name = elem.GetAttribute("name");
389 int minPc, maxPc, minPts, maxPts, minChoices, maxChoices, baseValue, incValue, incAmount;
390
391 try
392 {
393 minPc = int.Parse(elem.GetAttribute("minPercentage"));
394 }
395 catch(FormatException)
396 {
397 throw new FormatException("Attribute 'minPercentage' of category "+id+" was not a valid number");
398 }
399
400 try
401 {
402 maxPc = int.Parse(elem.GetAttribute("maxPercentage"));
403 }
404 catch(FormatException)
405 {
406 throw new FormatException("Attribute 'maxPercentage' of category "+id+" was not a valid number");
407 }
408
409 try
410 {
411 minPts = int.Parse(elem.GetAttribute("minPoints"));
412 }
413 catch(FormatException)
414 {
415 throw new FormatException("Attribute 'minPoints' of category "+id+" was not a valid number");
416 }
417
418 try
419 {
420 maxPts = int.Parse(elem.GetAttribute("maxPoints"));
421 }
422 catch(FormatException)
423 {
424 throw new FormatException("Attribute 'maxPoints' of category "+id+" was not a valid number");
425 }
426
427 try
428 {
429 minChoices = int.Parse(elem.GetAttribute("minChoices"));
430 }
431 catch(FormatException)
432 {
433 throw new FormatException("Attribute 'minChoices' of category "+id+" was not a valid number");
434 }
435
436 try
437 {
438 maxChoices = int.Parse(elem.GetAttribute("maxChoices"));
439 }
440 catch(FormatException)
441 {
442 throw new FormatException("Attribute 'maxChoices' of category "+id+" was not a valid number");
443 }
444
445 try
446 {
447 baseValue = int.Parse(elem.GetAttribute("baseValue"));
448
449 }
450 catch(FormatException)
451 {
452 throw new FormatException("Attribute 'baseValue' of category "+id+" was not a valid number");
453 }
454
455 try
456 {
457 incValue = int.Parse(elem.GetAttribute("incValue"));
458 }
459 catch(FormatException)
460 {
461 throw new FormatException("Attribute 'incValue' of category "+id+" was not a valid number");
462 }
463
464 try
465 {
466 incAmount = int.Parse(elem.GetAttribute("incAmount"));
467 }
468 catch(FormatException)
469 {
470 throw new FormatException("Attribute 'incAmount' of category "+id+" was not a valid number");
471 }
472
473 return new Category(id, name, minPts, maxPts, minPc, maxPc, minChoices, maxChoices, baseValue, incValue, incAmount);
474 }
475
476 private UnitType CreateUnitTypeFromElement(XmlElement elem, Race parentRace, GameSystem system)
477 {
478 string id = elem.GetAttribute("id");
479 string name = elem.GetAttribute("typeName");
480 string mainCatID = elem.GetAttribute("cat");
481 int minNum, maxNum, minSize, maxSize, baseSize;//TODO: Add base size
482 float points, unitPoints;
483 Stats stats;
484 List<UnitRequirement> unitRequirements = new List<UnitRequirement>();
485 bool found = false;
486 List<string> catIDs = new List<string>();
487 string catID;
488
489 try
490 {
491 minNum = int.Parse(elem.GetAttribute("minNum"));
492 }
493 catch(FormatException)
494 {
495 throw new FormatException("Attribute 'minNum' of unit "+id+" was not a valid number");
496 }
497
498 try
499 {
500 maxNum = int.Parse(elem.GetAttribute("maxNum"));
501 }
502 catch(FormatException)
503 {
504 throw new FormatException("Attribute 'maxNum' of unit "+id+" was not a valid number");
505 }
506
507 try
508 {
509 minSize = int.Parse(elem.GetAttribute("minSize"));
510 }
511 catch(FormatException)
512 {
513 throw new FormatException("Attribute 'minSize' of unit "+id+" was not a valid number");
514 }
515
516 try
517 {
518 maxSize = int.Parse(elem.GetAttribute("maxSize"));
519 }
520 catch(FormatException)
521 {
522 throw new FormatException("Attribute 'maxSize' of unit "+id+" was not a valid number");
523 }
524
525 if (minSize > maxSize && maxSize!=-1)
526 {
527 minSize = maxSize;
528 }
529
530 try
531 {
532 points = int.Parse(elem.GetAttribute("points"));
533 }
534 catch(FormatException)
535 {
536 throw new FormatException("Attribute 'points' of unit "+id+" was not a valid number");
537 }
538
539 try
540 {
541 unitPoints = int.Parse(elem.GetAttribute("unitPoints"));
542 }
543 catch(FormatException)
544 {
545 throw new FormatException("Attribute 'trooperPoints' of unit "+id+" was not a valid number");
546 }
547
548 XmlNode node = elem.FirstChild;
549
550 foreach(XmlElement cat in node.ChildNodes)
551 {
552 catID = cat.GetAttribute("catID");
553 catIDs.Add(catID);
554
555 if (catID == mainCatID)
556 {
557 found = true;
558 }
559 }
560
561 if (!found)
562 {
563 throw new InvalidFileException("The main cat "+mainCatID+" was not found in the list of categories for unit "+id);
564 }
565
566 node = node.NextSibling;
567 stats = ParseUnitStats((XmlElement)node, system);
568 //TODO: Add unit requirements
569 UnitType type = new UnitType(id, name, mainCatID, catIDs.ToArray(), minNum, maxNum, minSize, maxSize, unitPoints, points, stats, unitRequirements.ToArray(), parentRace);
570
571 return type;
572 }
573
574 private Stats ParseUnitStats(XmlElement elem, GameSystem system)
575 {
576 List<Stat> statsList = new List<Stat>();
577 String statsID = elem.GetAttribute("statSet");
578 SystemStats statsSet;
579
580 if (statsID == "")
581 {
582 statsSet = system.StandardSystemStats;
583 }
584 else
585 {
586 statsSet = system.SystemStats[statsID];
587 }
588
589 Stats stats = new Stats(statsSet);
590
591 foreach (XmlElement stat in elem.ChildNodes)
592 {
593 String statID = stat.GetAttribute("name");
594 StatSlot slot = statsSet[statID];
595
596 if (slot!=null)
597 {
598 statsList.Add(new Stat(slot, stat.InnerText));
599 }
600 else
601 {
602 throw new InvalidFileException("The stat "+statID+" was not found in stats set "+statsID);
603 }
604 }
605
606 stats.SetStats(statsList);
607
608 return stats;
609 }
610
611 private Dictionary<string, SystemStats> CreateSystemStatsSetFromElement(XmlElement elem)
612 {
613 Dictionary<string, SystemStats> dict = new Dictionary<string,SystemStats>();
614
615 foreach (XmlElement stats in elem.ChildNodes)
616 {
617 SystemStats sysStats = CreateSystemStatsFromElement(stats);
618 dict.Add(sysStats.ID, sysStats);
619 }
620
621 return dict;
622 }
623
624 private SystemStats CreateSystemStatsFromElement(XmlElement elem)
625 {
626 List<StatSlot> slots = new List<StatSlot>();
627 string id = elem.GetAttribute("id");
628
629 foreach (XmlElement slot in elem.ChildNodes)
630 {
631 StatSlot statSlot = new StatSlot(slot.GetAttribute("name"));
632 slots.Add(statSlot);
633 }
634
635 return new SystemStats(id, slots.ToArray());
636 }
637
638 private EquipmentItem CreateEquipmentItemFromElement(XmlElement elem, Race race)
639 {
640 string id = elem.GetAttribute("id");
641 string name = elem.GetAttribute("name");
642 float cost = 0, min = 0, max = 0;
643 ArmourType armourType;
644
645 try
646 {
647 cost = float.Parse(elem.GetAttribute("cost"));
648 }
649 catch(FormatException)
650 {
651 throw new FormatException("Attribute 'cost' of equipment item "+id+" was not a valid number");
652 }
653
654 try
655 {
656 min = float.Parse(elem.GetAttribute("min"));
657 }
658 catch(FormatException)
659 {
660 throw new FormatException("Attribute 'min' of equipment item "+id+" was not a valid number");
661 }
662
663 try
664 {
665 max = float.Parse(elem.GetAttribute("max"));
666 }
667 catch(FormatException)
668 {
669 throw new FormatException("Attribute 'max' of equipment item "+id+" was not a valid number");
670 }
671
672 try
673 {
674 armourType = (ArmourType)Enum.Parse(typeof(ArmourType), elem.GetAttribute("armourType"));
675 }
676 catch(FormatException)
677 {
678 throw new InvalidFileException("Attribute 'armourType' of equipment "+id+" was not a valid value from the enumeration");
679 }
680
681 if (elem.ChildNodes.Count>0)
682 {
683 //It has stats!
684 //TODO: Parse equipment stats
685 }
686
687 return new EquipmentItem(id, name, cost, min, max, armourType, race);
688 }
689
690 private void ValidationEventMethod(object sender, ValidationEventArgs e)
691 {
692 //TODO: Fire a validation failure event
693 LogNotifier.WarnFormat(GetType(), "Validation Error: {0}", e.Message);
694 }
695 }
696 }