changeset 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 1b35eed503ef
files IBBoard.WarFoundry.API.csproj api/Factories/Xml/AbstractStagedLoadedSubFactory.cs api/Factories/Xml/WarFoundryXmlArmyFactory.cs api/Factories/Xml/WarFoundryXmlFactory.cs api/Factories/Xml/WarFoundryXmlFactoryUtils.cs api/Factories/Xml/WarFoundryXmlGameSystemFactory.cs api/Factories/Xml/WarFoundryXmlRaceFactory.cs
diffstat 7 files changed, 521 insertions(+), 383 deletions(-) [+]
line wrap: on
line diff
--- a/IBBoard.WarFoundry.API.csproj	Sat Mar 28 21:00:35 2009 +0000
+++ b/IBBoard.WarFoundry.API.csproj	Mon Mar 30 19:44:03 2009 +0000
@@ -120,6 +120,11 @@
     <Compile Include="api\WarFoundryCore.cs" />
     <Compile Include="api\WarFoundryLoader.cs" />
     <Compile Include="AssemblyInfo.cs" />
+    <Compile Include="api\Factories\Xml\WarFoundryXmlGameSystemFactory.cs" />
+    <Compile Include="api\Factories\Xml\WarFoundryXmlRaceFactory.cs" />
+    <Compile Include="api\Factories\Xml\WarFoundryXmlArmyFactory.cs" />
+    <Compile Include="api\Factories\Xml\WarFoundryXmlFactoryUtils.cs" />
+    <Compile Include="api\Factories\Xml\AbstractStagedLoadedSubFactory.cs" />
   </ItemGroup>
   <ItemGroup>
     <Content Include="libs\ICSharpCode.SharpZipLib.dll" />
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/api/Factories/Xml/AbstractStagedLoadedSubFactory.cs	Mon Mar 30 19:44:03 2009 +0000
@@ -0,0 +1,34 @@
+//  This file (AbstractStagedLoadedSubFactory.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2009 IBBoard
+// 
+//  The file and the library/program it is in are licensed under the GNU LGPL license, either version 3 of the License or (at your option) any later version. Please see COPYING.LGPL for more information and the full license.
+// 
+
+using System;
+using System.Xml;
+using IBBoard.Xml;
+using IBBoard.WarFoundry.API.Objects;
+
+namespace IBBoard.WarFoundry.API.Factories.Xml
+{
+	public class AbstractStagedLoadedSubFactory
+	{
+		protected WarFoundryXmlFactory mainFactory;
+		
+		protected AbstractStagedLoadedSubFactory(WarFoundryXmlFactory factory)
+		{
+			mainFactory = factory;
+		}
+		
+		protected Category CreateCategoryFromElement(XmlElement elem)
+		{
+			string id = elem.GetAttribute("id");
+			string name = elem.GetAttribute("name");
+			Category cat = new Category(id, name);
+			cat.MaximumPercentage = XmlTools.GetIntValueFromAttribute(elem, "maxPercentage");
+			cat.MinimumPercentage = XmlTools.GetIntValueFromAttribute(elem, "minPercentage");
+			cat.MaximumPoints = XmlTools.GetIntValueFromAttribute(elem, "maxPoints");
+			cat.MinimumPoints = XmlTools.GetIntValueFromAttribute(elem, "minPoints");
+			return cat;
+		}	
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/api/Factories/Xml/WarFoundryXmlArmyFactory.cs	Mon Mar 30 19:44:03 2009 +0000
@@ -0,0 +1,32 @@
+//  This file (WarFoundryXmlArmyFactory.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2009 IBBoard
+// 
+//  The file and the library/program it is in are licensed under the GNU LGPL license, either version 3 of the License or (at your option) any later version. Please see COPYING.LGPL for more information and the full license.
+// 
+
+using System;
+using System.Xml;
+using IBBoard.Xml;
+using ICSharpCode.SharpZipLib.Zip;
+using IBBoard.WarFoundry.API.Objects;
+
+namespace IBBoard.WarFoundry.API.Factories.Xml
+{
+	/// <summary>
+	/// A sub-factory for loading WarFoundry Army XML files
+	/// </summary>
+	public class WarFoundryXmlArmyFactory
+	{			
+		public Army CreateArmyFromElement(ZipFile file, XmlElement elem)
+		{
+			string name = elem.GetAttribute("name");
+			string systemID = elem.GetAttribute("gameSystem");
+			GameSystem system = WarFoundryLoader.GetDefault().GetGameSystem(systemID);
+			string raceID = elem.GetAttribute("race");
+			Race race = WarFoundryLoader.GetDefault().GetRace(system, raceID);
+			int points = XmlTools.GetIntValueFromAttribute(elem, "maxPoints");			
+			Army army = new Army(race, name, points, file);
+			//TODO: Complete loading of army
+			return army;
+		}
+	}
+}
--- a/api/Factories/Xml/WarFoundryXmlFactory.cs	Sat Mar 28 21:00:35 2009 +0000
+++ b/api/Factories/Xml/WarFoundryXmlFactory.cs	Mon Mar 30 19:44:03 2009 +0000
@@ -26,9 +26,9 @@
 	public class WarFoundryXmlFactory : AbstractNativeWarFoundryFactory
 	{
 		private static WarFoundryXmlFactory factory;
-		private XmlReaderSettings settings;
-		private XmlNamespaceManager nsManager;
-		private Dictionary<IWarFoundryObject, XmlDocument> extraData = new Dictionary<IWarFoundryObject, XmlDocument>();
+		private WarFoundryXmlGameSystemFactory gameSystemFactory;
+		private WarFoundryXmlRaceFactory raceFactory;
+		private WarFoundryXmlArmyFactory armyFactory;
 
 		public static AbstractNativeWarFoundryFactory GetFactory()
 		{
@@ -42,7 +42,9 @@
 		
 		private WarFoundryXmlFactory() : base()
 		{
-			//Hide constructor
+			gameSystemFactory = new WarFoundryXmlGameSystemFactory(this);
+			raceFactory = new WarFoundryXmlRaceFactory(this);
+			armyFactory = new WarFoundryXmlArmyFactory();
 		}
 		
 		protected override bool CheckCanFindArmyFileContent(ZipFile file)
@@ -68,12 +70,12 @@
 		protected override Army CreateArmyFromStream (ZipFile file, Stream dataStream)
 		{
 			XmlElement elem = GetRootElementFromStream(dataStream, WarFoundryXmlElementName.ARMY_ELEMENT);
-			return CreateArmyFromElement(file, elem);
+			return armyFactory.CreateArmyFromElement(file, elem);
 		}
 		
 		private XmlElement GetRootElementFromStream(Stream stream, WarFoundryXmlElementName elementName)
 		{
-			XmlDocument doc = CreateXmlDocumentFromStream(stream);			
+			XmlDocument doc = WarFoundryXmlFactoryUtils.CreateXmlDocumentFromStream(stream);			
 			XmlElement elem = (XmlElement)doc.LastChild;
 			
 			if (!elem.LocalName.Equals(elementName.Value))
@@ -83,91 +85,6 @@
 			
 			return elem;
 		}
-		
-		private XmlDocument CreateXmlDocumentFromStream(Stream stream)
-		{
-			XmlDocument doc = new XmlDocument();
-			XmlReader reader = XmlReader.Create(stream, GetReaderSettings());
-			
-			try
-			{
-				doc.Load(reader);
-			}
-			//Don't catch XMLSchemaExceptions - let them get thrown out
-			finally
-			{
-				reader.Close();
-			}
-
-			return doc;
-		}
-		
-		/// <summary>
-		/// Lazy-getter for XML reader settings. May throw a <see cref="InvalidDataException"/> if there is a problem with the translation schema.
-		/// </summary>
-		/// <returns>
-		/// A <see cref="XmlReaderSettings"/> with the default values for validating the translation document against the translation schema
-		/// </returns>
-		private XmlReaderSettings GetReaderSettings()
-		{
-			if (settings == null)
-			{
-				settings = new XmlReaderSettings();
-				settings.ValidationType = ValidationType.Schema;
-				settings.ValidationFlags = XmlSchemaValidationFlags.ReportValidationWarnings;
-				settings.ProhibitDtd = true;
-				settings.ValidationEventHandler+= new ValidationEventHandler(ValidationEventMethod);
-				XmlSchemaSet cache = new XmlSchemaSet();
-				string path =  IBBoard.Constants.ExecutablePath + "/dtds/";
-				string nsBase = "http://ibboard.co.uk/warfoundry/";
-				AddSchemaToCache(cache, nsBase + "core", path + "warfoundry-core.xsd");
-				AddSchemaToCache(cache, nsBase + "cats", path + "warfoundry-cats.xsd");
-				AddSchemaToCache(cache, nsBase + "race", path + "race.xsd");
-				AddSchemaToCache(cache, nsBase + "system", path + "system.xsd");
-				AddSchemaToCache(cache, nsBase + "army", path + "army.xsd");
-				settings.Schemas.Add(cache);
-			}
-			
-			return settings;
-		}
-		
-		private void ValidationEventMethod(object sender, ValidationEventArgs e)
-		{
-			throw new InvalidDataException("Problem validating against schema for WarFoundry data: " + e.Exception.Message, e.Exception);
-		}
-		
-		private void AddSchemaToCache(XmlSchemaSet cache, string xmlNamespace, string schemaLocation)
-		{
-			try
-			{
-				cache.Add(xmlNamespace, schemaLocation);
-			}
-			catch (IOException ex)
-			{
-				LogNotifier.Warn(GetType(), "Problem reading schema: " + ex.Message, ex);
-			}
-			catch (XmlSchemaException ex)
-			{
-				LogNotifier.Warn(GetType(), "Problem validating schema for WarFoundry data: " + ex.Message, ex);
-			}
-			catch (XmlException ex)
-			{
-				LogNotifier.Warn(GetType(), "Problem reading data for schema: " + ex.Message, ex);
-			}
-		}
-		
-		private Army CreateArmyFromElement(ZipFile file, XmlElement elem)
-		{
-			string name = elem.GetAttribute("name");
-			string systemID = elem.GetAttribute("gameSystem");
-			GameSystem system = WarFoundryLoader.GetDefault().GetGameSystem(systemID);
-			string raceID = elem.GetAttribute("race");
-			Race race = WarFoundryLoader.GetDefault().GetRace(system, raceID);
-			int points = XmlTools.GetIntValueFromAttribute(elem, "maxPoints");			
-			Army army = new Army(race, name, points, file);
-			//TODO: Complete loading of army
-			return army;
-		}
 
 		protected override Stream GetGameSystemDataStream (ZipFile file)
 		{
@@ -178,21 +95,7 @@
 		{
 			XmlElement elem = GetRootElementFromStream(dataStream, WarFoundryXmlElementName.SYSTEM_ELEMENT);
 			LogNotifier.Debug(GetType(), "Create GameSystem");
-			return CreateSystemFromElement(file, elem);
-		}
-		
-		private GameSystem CreateSystemFromElement(ZipFile file, XmlElement elem)
-		{
-			string id = elem.GetAttribute("id");
-			string name = elem.GetAttribute("name");
-			GameSystem system = new GameSystem(id, name, this);
-			StoreExtraData(system, elem);
-			return system;
-		}
-		
-		private void StoreExtraData(WarFoundryStagedLoadingObject wfObject, XmlElement elem)
-		{
-			extraData[wfObject] = elem.OwnerDocument;
+			return gameSystemFactory.CreateSystemFromElement(file, elem);
 		}
 		
 		protected override Stream GetRaceDataStream (ZipFile file)
@@ -204,56 +107,7 @@
 		{
 			XmlElement elem = GetRootElementFromStream(dataStream, WarFoundryXmlElementName.RACE_ELEMENT);
 			LogNotifier.Debug(GetType(), "Create Race");
-			return CreateRaceFromElement(file, elem);
-		}
-		
-		private Race CreateRaceFromElement(ZipFile file, XmlElement elem)
-		{
-			string id = elem.GetAttribute("id");
-			string subid = elem.GetAttribute("subid");
-			string systemID = elem.GetAttribute("system");
-			string name = elem.GetAttribute("name");
-			Race race = new Race(id, subid, name, systemID, this);
-			StoreExtraData(race, elem);
-			return race;
-		}
-	
-		public XmlDocument GetExtraData(IWarFoundryObject obj)
-		{
-			XmlDocument extra = null;
-			extraData.TryGetValue(obj, out extra);			
-			return extra;
-		}
-		
-		private XmlNamespaceManager GetNamespaceManager()
-		{
-			if (nsManager == null)
-			{
-				nsManager = new XmlNamespaceManager(new NameTable());
-				nsManager.AddNamespace("core", "http://ibboard.co.uk/warfoundry/core");
-				nsManager.AddNamespace("cat", "http://ibboard.co.uk/warfoundry/cats");
-				nsManager.AddNamespace("race", "http://ibboard.co.uk/warfoundry/race");
-				nsManager.AddNamespace("system", "http://ibboard.co.uk/warfoundry/system");
-				nsManager.AddNamespace("army", "http://ibboard.co.uk/warfoundry/army");
-			}
-			
-			return nsManager;
-		}
-		
-		private XmlNodeList SelectNodes(XmlNode element, string xpathQuery)
-		{
-			return element.SelectNodes(xpathQuery, GetNamespaceManager());
-		}
-		
-		private XmlNode SelectSingleNode(XmlNode element, string xpathQuery)
-		{
-			return element.SelectSingleNode(xpathQuery, GetNamespaceManager());
-		}
-		
-		private XmlElement SelectSingleElement(XmlNode element, string xpathQuery)
-		{
-			XmlNode node = SelectSingleNode(element, xpathQuery);
-			return (node is XmlElement) ? (XmlElement) node : null;
+			return raceFactory.CreateRaceFromElement(file, elem);
 		}
 
 		public override void CompleteLoading(IWarFoundryStagedLoadObject obj)
@@ -262,237 +116,12 @@
 							
 			if (obj is GameSystem)
 			{
-				CompleteLoading((GameSystem)obj);
+				gameSystemFactory.CompleteLoading((GameSystem)obj);
 			}
 			else if (obj is Race)
 			{
-				CompleteLoading((Race)obj);
-			}
-		}
-		
-		public void CompleteLoading(GameSystem system)
-		{
-			if (!CanCompleteLoading(system))
-			{
-				return;
-			}
-			
-			system.SetAsLoading();			
-			XmlDocument extraData = GetExtraData(system);
-			LoadCategoriesForSystem(system, extraData);
-			XmlElement statsElem = SelectSingleElement(extraData, "/system:system/system:sysStatsList");
-			string defaultStatsID = statsElem.GetAttribute("defaultStats");
-			LoadSystemStatsForSystem(system, extraData);
-			system.StandardSystemStatsID = defaultStatsID;
-			XmlElement systemElement = SelectSingleElement(extraData, "/system:system");
-			system.WarnOnError = XmlTools.GetBoolValueFromAttribute(systemElement, "warn");
-			system.AllowAllies = XmlTools.GetBoolValueFromAttribute(systemElement, "allowAllies");
-			LogNotifier.DebugFormat(GetType(), "Completed loading of GameSystem with ID {0}", system.ID);
-			LogNotifier.DebugFormat(GetType(), "GameSystem with ID {0} default stats: {1}", system.ID, system.StandardSystemStatsID);
-			system.SetAsFullyLoaded();
-		}
-		
-		private bool CanCompleteLoading(IWarFoundryStagedLoadObject obj)
-		{
-			bool canLoad = true;			
-			
-			if (obj.IsFullyLoaded)
-			{
-				LogNotifier.DebugFormat(GetType(), "Object of type {0} with ID {1} is already fully loaded", obj.GetType().Name, obj.ID);
-				canLoad = false;
-			}
-			else if (obj.IsLoading)
-			{
-				LogNotifier.WarnFormat(GetType(), "Object of type {0} with ID {1} is already being loaded", obj.GetType().Name, obj.ID);
-				canLoad = false;
-			}
-			
-			return canLoad;
-		}
-		
-		private void LoadCategoriesForSystem(GameSystem system, XmlNode elem)
-		{
-			foreach (XmlElement cat in SelectNodes(elem, "/system:system/system:categories/cat:cat"))
-			{
-				system.AddCategory(CreateCategoryFromElement(cat));
-			}
-		}
-		
-		private Category CreateCategoryFromElement(XmlElement elem)
-		{
-			string id = elem.GetAttribute("id");
-			string name = elem.GetAttribute("name");
-			Category cat = new Category(id, name);
-			cat.MaximumPercentage = XmlTools.GetIntValueFromAttribute(elem, "maxPercentage");
-			cat.MinimumPercentage = XmlTools.GetIntValueFromAttribute(elem, "minPercentage");
-			cat.MaximumPoints = XmlTools.GetIntValueFromAttribute(elem, "maxPoints");
-			cat.MinimumPoints = XmlTools.GetIntValueFromAttribute(elem, "minPoints");
-			return cat;
-		}		
-		
-		private void LoadSystemStatsForSystem(GameSystem system, XmlNode elem)
-		{
-			foreach (XmlElement stats in SelectNodes(elem, "/system:system/system:sysStatsList/system:sysStats"))
-			{
-				SystemStats sysStats = CreateSystemStatsFromElement(stats);
-				system.AddSystemStats(sysStats);
-			}
-		}
-		
-		private SystemStats CreateSystemStatsFromElement(XmlElement elem)
-		{
-			List<StatSlot> slots = new List<StatSlot>();
-			string id = elem.GetAttribute("id");	
-			
-			foreach (XmlElement slot in elem.ChildNodes)
-			{
-				StatSlot statSlot = new StatSlot(slot.GetAttribute("name"));
-				slots.Add(statSlot);
-			}
-			
-			return new SystemStats(id, slots.ToArray());
-		}
-		
-		public void CompleteLoading(Race race)
-		{
-			if (!CanCompleteLoading(race))
-			{
-				return;
-			}
-			
-			race.SetAsLoading();			
-			XmlDocument extraData = GetExtraData(race);
-			
-			foreach (XmlElement node in SelectNodes(extraData, "/race:race/race:units/race:unit"))
-			{
-				UnitType type = CreateUnitTypeFromElement(node, race, race.GameSystem);
-				race.AddUnitType(type);
-			}
-			
-			foreach (XmlElement node in SelectNodes(extraData, "/race:race/race:categories/cat:cat"))
-			{
-				race.AddCategory(CreateCategoryFromElement(node));
-			}
-							
-			foreach (XmlElement node  in SelectNodes(extraData, "/race:race/race:equipment/cat:equipmentItem"))
-			{
-				EquipmentItem item = CreateEquipmentItemFromElement(node, race);
-				race.AddEquipmentItem(item);
+				raceFactory.CompleteLoading((Race)obj);
 			}
-							
-			foreach (XmlElement node  in SelectNodes(extraData, "/race:race/race:abilities/cat:ability"))
-			{
-				Ability ability = CreateAbilityFromElement(node, race);
-				race.AddAbility(ability);
-			}
-			
-			race.SetAsFullyLoaded();
-			LogNotifier.DebugFormat(GetType(), "Completed loading of Race with ID {0}", race.ID);
-		}
-						
-		private UnitType CreateUnitTypeFromElement(XmlElement elem, Race parentRace, GameSystem system)
-		{
-			string id = elem.GetAttribute("id");
-			string name = elem.GetAttribute("typeName");
-			UnitType type = new UnitType(id, name, parentRace);
-			type.MaxNumber = XmlTools.GetIntValueFromAttribute(elem, "maxNum");
-			type.MinNumber = XmlTools.GetIntValueFromAttribute(elem, "minNum");
-			type.MaxSize = XmlTools.GetIntValueFromAttribute(elem, "maxSize");
-			type.MinSize = XmlTools.GetIntValueFromAttribute(elem, "minSize");
-			type.BaseSize = XmlTools.GetIntValueFromAttribute(elem, "baseSize");
-			type.CostPerTrooper = XmlTools.GetIntValueFromAttribute(elem, "points");
-			type.BaseUnitCost = XmlTools.GetIntValueFromAttribute(elem, "unitPoints");
-			string mainCatID = elem.GetAttribute("cat");
-			Category cat = parentRace.GetCategory(mainCatID);
-			
-			if (cat == null)
-			{
-				throw new InvalidDataException(String.Format("Attribute 'cat' of UnitType {0} (value: {1}) did not reference a valid category", id, mainCatID));
-			}
-			
-			type.MainCategory = cat;
-			XmlElement statsElement = SelectSingleElement(elem, "/race:race/race:units/race:unit/race:stats");
-			type.UnitStats = ParseUnitStats(statsElement, system);
-			//TODO: Add unit requirements
-			LogNotifier.Debug(GetType(), "Loaded "+type.Name);
-			return type;
-		}
-		
-		private Stats ParseUnitStats(XmlElement elem, GameSystem system)
-		{
-			List<Stat> statsList = new List<Stat>();
-			String statsID = elem.GetAttribute("statSet");
-			SystemStats statsSet;
-			
-			if (statsID == "")
-			{
-				statsSet = system.StandardSystemStats;
-			}
-			else
-			{
-				statsSet = system.GetSystemStatsForID(statsID);
-			}
-			
-			Stats stats = new Stats(statsSet);
-			
-			foreach (XmlElement stat in elem.ChildNodes)
-			{
-				String statID = stat.GetAttribute("name");
-				StatSlot slot = statsSet[statID];
-				
-				if (slot!=null)
-				{
-					statsList.Add(new Stat(slot, stat.InnerText));
-				}
-				else
-				{
-					throw new InvalidFileException("The stat "+statID+" was not found in stats set "+statsID);
-				}
-			}
-			
-			stats.SetStats(statsList);
-			
-			return stats;
-		}
-		
-		private EquipmentItem CreateEquipmentItemFromElement(XmlElement elem, Race race)
-		{
-			string id = elem.GetAttribute("id");
-			string name = elem.GetAttribute("name");
-			double cost = 0, min = 0, max = 0;
-			ArmourType armourType;
-			
-			try
-			{
-				cost = XmlTools.GetDoubleValueFromAttribute(elem, "cost");
-			}
-			catch(FormatException ex)
-			{
-				throw new InvalidFileException("Attribute 'cost' of equipment item "+id+" was not a valid number", ex);
-			}			
-			
-			try
-			{
-				armourType = (ArmourType)Enum.Parse(typeof(ArmourType), elem.GetAttribute("armourType"));
-			}
-			catch(ArgumentException ex)
-			{
-				throw new InvalidFileException("Attribute 'armourType' of equipment "+id+" was not a valid value from the enumeration", ex);
-			}
-			
-			//TODO: Parse equipment stats if there are any
-			
-			return new EquipmentItem(id, name, cost, min, max, armourType, race);
-		}
-		
-		private Ability CreateAbilityFromElement(XmlElement elem, Race race)
-		{
-			string id = elem.GetAttribute("id");
-			string name = elem.GetAttribute("name");
-			Ability ability = new Ability(id, name);
-			XmlNode node = elem.SelectSingleNode("description", GetNamespaceManager());
-			ability.Description = (node == null) ? "" : node.InnerText;
-			return ability;
 		}
 	}
 }
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/api/Factories/Xml/WarFoundryXmlFactoryUtils.cs	Mon Mar 30 19:44:03 2009 +0000
@@ -0,0 +1,144 @@
+//  This file (WarFoundryXmlFactoryUtils.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2009 IBBoard
+// 
+//  The file and the library/program it is in are licensed under the GNU LGPL license, either version 3 of the License or (at your option) any later version. Please see COPYING.LGPL for more information and the full license.
+// 
+
+using System;
+using System.IO;
+using System.Xml;
+using System.Xml.Schema;
+using IBBoard.Logging;
+using IBBoard.WarFoundry.API.Objects;
+
+namespace IBBoard.WarFoundry.API.Factories.Xml
+{
+	/// <summary>
+	/// A collection of useful utility methods for loading WarFoundry data from XML files
+	/// </summary>
+	public class WarFoundryXmlFactoryUtils
+	{
+		private static XmlReaderSettings settings;
+		private static XmlNamespaceManager nsManager;
+		
+		public static XmlNodeList SelectNodes(XmlNode element, string xpathQuery)
+		{
+			return element.SelectNodes(xpathQuery, GetNamespaceManager());
+		}
+		
+		public static XmlNode SelectSingleNode(XmlNode element, string xpathQuery)
+		{
+			return element.SelectSingleNode(xpathQuery, GetNamespaceManager());
+		}
+		
+		public static XmlElement SelectSingleElement(XmlNode element, string xpathQuery)
+		{
+			XmlNode node = SelectSingleNode(element, xpathQuery);
+			return (node is XmlElement) ? (XmlElement) node : null;
+		}
+				
+		public static XmlNamespaceManager GetNamespaceManager()
+		{
+			if (nsManager == null)
+			{
+				nsManager = new XmlNamespaceManager(new NameTable());
+				nsManager.AddNamespace("core", "http://ibboard.co.uk/warfoundry/core");
+				nsManager.AddNamespace("cat", "http://ibboard.co.uk/warfoundry/cats");
+				nsManager.AddNamespace("race", "http://ibboard.co.uk/warfoundry/race");
+				nsManager.AddNamespace("system", "http://ibboard.co.uk/warfoundry/system");
+				nsManager.AddNamespace("army", "http://ibboard.co.uk/warfoundry/army");
+			}
+			
+			return nsManager;
+		}
+		
+		/// <summary>
+		/// Lazy-getter for XML reader settings. May throw a <see cref="InvalidDataException"/> if there is a problem with the translation schema.
+		/// </summary>
+		/// <returns>
+		/// A <see cref="XmlReaderSettings"/> with the default values for validating the translation document against the translation schema
+		/// </returns>
+		public static XmlReaderSettings GetReaderSettings()
+		{
+			if (settings == null)
+			{
+				settings = new XmlReaderSettings();
+				settings.ValidationType = ValidationType.Schema;
+				settings.ValidationFlags = XmlSchemaValidationFlags.ReportValidationWarnings;
+				settings.ProhibitDtd = true;
+				settings.ValidationEventHandler+= new ValidationEventHandler(ValidationEventMethod);
+				XmlSchemaSet cache = new XmlSchemaSet();
+				string path =  IBBoard.Constants.ExecutablePath + "/dtds/";
+				string nsBase = "http://ibboard.co.uk/warfoundry/";
+				AddSchemaToCache(cache, nsBase + "core", path + "warfoundry-core.xsd");
+				AddSchemaToCache(cache, nsBase + "cats", path + "warfoundry-cats.xsd");
+				AddSchemaToCache(cache, nsBase + "race", path + "race.xsd");
+				AddSchemaToCache(cache, nsBase + "system", path + "system.xsd");
+				AddSchemaToCache(cache, nsBase + "army", path + "army.xsd");
+				settings.Schemas.Add(cache);
+			}
+			
+			return settings;
+		}
+		
+		private static void ValidationEventMethod(object sender, ValidationEventArgs e)
+		{
+			throw new InvalidDataException("Problem validating against schema for WarFoundry data: " + e.Exception.Message, e.Exception);
+		}
+		
+		private static void AddSchemaToCache(XmlSchemaSet cache, string xmlNamespace, string schemaLocation)
+		{
+			try
+			{
+				cache.Add(xmlNamespace, schemaLocation);
+			}
+			catch (IOException ex)
+			{
+				LogNotifier.Warn(typeof(WarFoundryXmlFactoryUtils), "Problem reading schema: " + ex.Message, ex);
+			}
+			catch (XmlSchemaException ex)
+			{
+				LogNotifier.Warn(typeof(WarFoundryXmlFactoryUtils), "Problem validating schema for WarFoundry data: " + ex.Message, ex);
+			}
+			catch (XmlException ex)
+			{
+				LogNotifier.Warn(typeof(WarFoundryXmlFactoryUtils), "Problem reading data for schema: " + ex.Message, ex);
+			}
+		}
+		
+		public static XmlDocument CreateXmlDocumentFromStream(Stream stream)
+		{
+			XmlDocument doc = new XmlDocument();
+			XmlReader reader = XmlReader.Create(stream, GetReaderSettings());
+			
+			try
+			{
+				doc.Load(reader);
+			}
+			//Don't catch XMLSchemaExceptions - let them get thrown out
+			finally
+			{
+				reader.Close();
+			}
+
+			return doc;
+		}
+		
+		public static bool CanCompleteLoading(IWarFoundryStagedLoadObject obj)
+		{
+			bool canLoad = true;			
+			
+			if (obj.IsFullyLoaded)
+			{
+				LogNotifier.DebugFormat(typeof(WarFoundryXmlFactoryUtils), "Object of type {0} with ID {1} is already fully loaded", obj.GetType().Name, obj.ID);
+				canLoad = false;
+			}
+			else if (obj.IsLoading)
+			{
+				LogNotifier.WarnFormat(typeof(WarFoundryXmlFactoryUtils), "Object of type {0} with ID {1} is already being loaded", obj.GetType().Name, obj.ID);
+				canLoad = false;
+			}
+			
+			return canLoad;
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/api/Factories/Xml/WarFoundryXmlGameSystemFactory.cs	Mon Mar 30 19:44:03 2009 +0000
@@ -0,0 +1,100 @@
+//  This file (WarFoundryXmlGameSystemFactory.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2009 IBBoard
+// 
+//  The file and the library/program it is in are licensed under the GNU LGPL license, either version 3 of the License or (at your option) any later version. Please see COPYING.LGPL for more information and the full license.
+// 
+
+using System;
+using System.Collections.Generic;
+using System.Xml;
+using ICSharpCode.SharpZipLib.Zip;
+using IBBoard.Logging;
+using IBBoard.Xml;
+using IBBoard.WarFoundry.API.Objects;
+
+namespace IBBoard.WarFoundry.API.Factories.Xml
+{
+	/// <summary>
+	/// A sub-factory specifically for loading GameSystems from WarFoundry XML files
+	/// </summary>
+	public class WarFoundryXmlGameSystemFactory : AbstractStagedLoadedSubFactory
+	{	
+		private Dictionary<GameSystem, XmlDocument> extraData = new Dictionary<GameSystem, XmlDocument>();
+		
+		public WarFoundryXmlGameSystemFactory(WarFoundryXmlFactory factory) : base(factory)
+		{
+		}
+		
+		private void StoreExtraData(GameSystem wfObject, XmlElement elem)
+		{
+			extraData[wfObject] = elem.OwnerDocument;
+		}
+	
+		private XmlDocument GetExtraData(GameSystem obj)
+		{
+			XmlDocument extra = null;
+			extraData.TryGetValue(obj, out extra);			
+			return extra;
+		}
+		
+		public GameSystem CreateSystemFromElement(ZipFile file, XmlElement elem)
+		{
+			string id = elem.GetAttribute("id");
+			string name = elem.GetAttribute("name");
+			GameSystem system = new GameSystem(id, name, mainFactory);
+			StoreExtraData(system, elem);
+			return system;
+		}		
+		
+		public void CompleteLoading(GameSystem system)
+		{
+			if (!WarFoundryXmlFactoryUtils.CanCompleteLoading(system))
+			{
+				return;
+			}
+			
+			system.SetAsLoading();			
+			XmlDocument extraData = GetExtraData(system);
+			LoadCategoriesForSystem(system, extraData);
+			XmlElement statsElem = WarFoundryXmlFactoryUtils.SelectSingleElement(extraData, "/system:system/system:sysStatsList");
+			string defaultStatsID = statsElem.GetAttribute("defaultStats");
+			LoadSystemStatsForSystem(system, extraData);
+			system.StandardSystemStatsID = defaultStatsID;
+			XmlElement systemElement = WarFoundryXmlFactoryUtils.SelectSingleElement(extraData, "/system:system");
+			system.WarnOnError = XmlTools.GetBoolValueFromAttribute(systemElement, "warn");
+			system.AllowAllies = XmlTools.GetBoolValueFromAttribute(systemElement, "allowAllies");
+			LogNotifier.DebugFormat(GetType(), "Completed loading of GameSystem with ID {0}", system.ID);
+			LogNotifier.DebugFormat(GetType(), "GameSystem with ID {0} default stats: {1}", system.ID, system.StandardSystemStatsID);
+			system.SetAsFullyLoaded();
+		}
+		
+		private void LoadCategoriesForSystem(GameSystem system, XmlNode elem)
+		{
+			foreach (XmlElement cat in WarFoundryXmlFactoryUtils.SelectNodes(elem, "/system:system/system:categories/cat:cat"))
+			{
+				system.AddCategory(CreateCategoryFromElement(cat));
+			}
+		}	
+		
+		private void LoadSystemStatsForSystem(GameSystem system, XmlNode elem)
+		{
+			foreach (XmlElement stats in WarFoundryXmlFactoryUtils.SelectNodes(elem, "/system:system/system:sysStatsList/system:sysStats"))
+			{
+				SystemStats sysStats = CreateSystemStatsFromElement(stats);
+				system.AddSystemStats(sysStats);
+			}
+		}
+		
+		private SystemStats CreateSystemStatsFromElement(XmlElement elem)
+		{
+			List<StatSlot> slots = new List<StatSlot>();
+			string id = elem.GetAttribute("id");	
+			
+			foreach (XmlElement slot in elem.ChildNodes)
+			{
+				StatSlot statSlot = new StatSlot(slot.GetAttribute("name"));
+				slots.Add(statSlot);
+			}
+			
+			return new SystemStats(id, slots.ToArray());
+		}	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/api/Factories/Xml/WarFoundryXmlRaceFactory.cs	Mon Mar 30 19:44:03 2009 +0000
@@ -0,0 +1,194 @@
+//  This file (WarFoundryXmlRaceFactory.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2009 IBBoard
+// 
+//  The file and the library/program it is in are licensed under the GNU LGPL license, either version 3 of the License or (at your option) any later version. Please see COPYING.LGPL for more information and the full license.
+// 
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Xml;
+using IBBoard.Xml;
+using IBBoard.IO;
+using IBBoard.Logging;
+using ICSharpCode.SharpZipLib.Zip;
+using IBBoard.WarFoundry.API.Objects;
+
+namespace IBBoard.WarFoundry.API.Factories.Xml
+{
+	/// <summary>
+	/// A sub-factory for loading WarFoundry Race XML files
+	/// </summary>
+	public class WarFoundryXmlRaceFactory : AbstractStagedLoadedSubFactory
+	{
+		private Dictionary<Race, XmlDocument> extraData = new Dictionary<Race, XmlDocument>();
+		
+		public WarFoundryXmlRaceFactory(WarFoundryXmlFactory factory) : base (factory)
+		{
+		}
+		
+		private void StoreExtraData(Race wfObject, XmlElement elem)
+		{
+			extraData[wfObject] = elem.OwnerDocument;
+		}
+	
+		private XmlDocument GetExtraData(Race obj)
+		{
+			XmlDocument extra = null;
+			extraData.TryGetValue(obj, out extra);			
+			return extra;
+		}
+		
+		public Race CreateRaceFromElement(ZipFile file, XmlElement elem)
+		{
+			string id = elem.GetAttribute("id");
+			string subid = elem.GetAttribute("subid");
+			string systemID = elem.GetAttribute("system");
+			string name = elem.GetAttribute("name");
+			Race race = new Race(id, subid, name, systemID, mainFactory);
+			StoreExtraData(race, elem);
+			return race;
+		}
+		
+		public void CompleteLoading(Race race)
+		{
+			if (!WarFoundryXmlFactoryUtils.CanCompleteLoading(race))
+			{
+				return;
+			}
+			
+			race.SetAsLoading();			
+			XmlDocument extraData = GetExtraData(race);
+			
+			foreach (XmlElement node in WarFoundryXmlFactoryUtils.SelectNodes(extraData, "/race:race/race:units/race:unit"))
+			{
+				UnitType type = CreateUnitTypeFromElement(node, race, race.GameSystem);
+				race.AddUnitType(type);
+			}
+			
+			foreach (XmlElement node in WarFoundryXmlFactoryUtils.SelectNodes(extraData, "/race:race/race:categories/cat:cat"))
+			{
+				race.AddCategory(CreateCategoryFromElement(node));
+			}
+							
+			foreach (XmlElement node  in WarFoundryXmlFactoryUtils.SelectNodes(extraData, "/race:race/race:equipment/cat:equipmentItem"))
+			{
+				EquipmentItem item = CreateEquipmentItemFromElement(node, race);
+				race.AddEquipmentItem(item);
+			}
+							
+			foreach (XmlElement node  in WarFoundryXmlFactoryUtils.SelectNodes(extraData, "/race:race/race:abilities/cat:ability"))
+			{
+				Ability ability = CreateAbilityFromElement(node, race);
+				race.AddAbility(ability);
+			}
+			
+			race.SetAsFullyLoaded();
+			LogNotifier.DebugFormat(GetType(), "Completed loading of Race with ID {0}", race.ID);
+		}
+						
+		private UnitType CreateUnitTypeFromElement(XmlElement elem, Race parentRace, GameSystem system)
+		{
+			string id = elem.GetAttribute("id");
+			string name = elem.GetAttribute("typeName");
+			UnitType type = new UnitType(id, name, parentRace);
+			type.MaxNumber = XmlTools.GetIntValueFromAttribute(elem, "maxNum");
+			type.MinNumber = XmlTools.GetIntValueFromAttribute(elem, "minNum");
+			type.MaxSize = XmlTools.GetIntValueFromAttribute(elem, "maxSize");
+			type.MinSize = XmlTools.GetIntValueFromAttribute(elem, "minSize");
+			type.BaseSize = XmlTools.GetIntValueFromAttribute(elem, "baseSize");
+			type.CostPerTrooper = XmlTools.GetIntValueFromAttribute(elem, "points");
+			type.BaseUnitCost = XmlTools.GetIntValueFromAttribute(elem, "unitPoints");
+			string mainCatID = elem.GetAttribute("cat");
+			Category cat = parentRace.GetCategory(mainCatID);
+			
+			if (cat == null)
+			{
+				throw new InvalidDataException(String.Format("Attribute 'cat' of UnitType {0} (value: {1}) did not reference a valid category", id, mainCatID));
+			}
+			
+			type.MainCategory = cat;
+			XmlElement statsElement = WarFoundryXmlFactoryUtils.SelectSingleElement(elem, "/race:race/race:units/race:unit/race:stats");
+			type.UnitStats = ParseUnitStats(statsElement, system);
+			//TODO: Add unit requirements
+			LogNotifier.Debug(GetType(), "Loaded "+type.Name);
+			return type;
+		}
+		
+		private Stats ParseUnitStats(XmlElement elem, GameSystem system)
+		{
+			List<Stat> statsList = new List<Stat>();
+			String statsID = elem.GetAttribute("statSet");
+			SystemStats statsSet;
+			
+			if (statsID == "")
+			{
+				statsSet = system.StandardSystemStats;
+			}
+			else
+			{
+				statsSet = system.GetSystemStatsForID(statsID);
+			}
+			
+			Stats stats = new Stats(statsSet);
+			
+			foreach (XmlElement stat in elem.ChildNodes)
+			{
+				String statID = stat.GetAttribute("name");
+				StatSlot slot = statsSet[statID];
+				
+				if (slot!=null)
+				{
+					statsList.Add(new Stat(slot, stat.InnerText));
+				}
+				else
+				{
+					throw new InvalidFileException("The stat "+statID+" was not found in stats set "+statsID);
+				}
+			}
+			
+			stats.SetStats(statsList);
+			
+			return stats;
+		}
+		
+		private EquipmentItem CreateEquipmentItemFromElement(XmlElement elem, Race race)
+		{
+			string id = elem.GetAttribute("id");
+			string name = elem.GetAttribute("name");
+			double cost = 0, min = 0, max = 0;
+			ArmourType armourType;
+			
+			try
+			{
+				cost = XmlTools.GetDoubleValueFromAttribute(elem, "cost");
+			}
+			catch(FormatException ex)
+			{
+				throw new InvalidFileException("Attribute 'cost' of equipment item "+id+" was not a valid number", ex);
+			}			
+			
+			try
+			{
+				armourType = (ArmourType)Enum.Parse(typeof(ArmourType), elem.GetAttribute("armourType"));
+			}
+			catch(ArgumentException ex)
+			{
+				throw new InvalidFileException("Attribute 'armourType' of equipment "+id+" was not a valid value from the enumeration", ex);
+			}
+			
+			//TODO: Parse equipment stats if there are any
+			
+			return new EquipmentItem(id, name, cost, min, max, armourType, race);
+		}
+		
+		private Ability CreateAbilityFromElement(XmlElement elem, Race race)
+		{
+			string id = elem.GetAttribute("id");
+			string name = elem.GetAttribute("name");
+			Ability ability = new Ability(id, name);
+			XmlNode node = elem.SelectSingleNode("description", WarFoundryXmlFactoryUtils.GetNamespaceManager());
+			ability.Description = (node == null) ? "" : node.InnerText;
+			return ability;
+		}
+	}
+}