/// <summary> /// Pass an XML document string in for processing. /// </summary> /// <param name="stringName">A human-readable identifier useful for debugging. Generally, the name of the file that the string was read from. Not required (but very useful.)</param> public void AddString(string input, string stringName = "(unnamed)") { // This is a really easy error to make; we might as well handle it. if (input.EndsWith(".xml")) { Dbg.Err($"It looks like you've passed the filename {input} to AddString instead of the actual XML file. Either use AddFile() or pass the file contents in."); } if (s_Status != Status.Accumulating) { Dbg.Err($"Adding data while while the world is in {s_Status} state; should be {Status.Accumulating} state"); } XDocument doc; try { doc = XDocument.Parse(input, LoadOptions.SetLineInfo); } catch (System.Xml.XmlException e) { Dbg.Ex(e); return; } if (doc.Elements().Count() > 1) { // This isn't testable, unfortunately; XDocument doesn't even support multiple root elements. Dbg.Err($"{stringName}: Found {doc.Elements().Count()} root elements instead of the expected 1"); } var readerContext = new ReaderContext(stringName, false); foreach (var rootElement in doc.Elements()) { if (rootElement.Name.LocalName != "Defs") { Dbg.Wrn($"{stringName}:{rootElement.LineNumber()}: Found root element with name \"{rootElement.Name.LocalName}\" when it should be \"Defs\""); } foreach (var defElement in rootElement.Elements()) { string typeName = defElement.Name.LocalName; Type typeHandle = UtilType.ParseDefFormatted(typeName, stringName, defElement.LineNumber()); if (typeHandle == null || !typeof(Def).IsAssignableFrom(typeHandle)) { Dbg.Err($"{stringName}:{defElement.LineNumber()}: {typeName} is not a valid root Def type"); continue; } if (defElement.Attribute("defName") == null) { Dbg.Err($"{stringName}:{defElement.LineNumber()}: No def name provided"); continue; } string defName = defElement.Attribute("defName").Value; if (!DefNameValidator.IsMatch(defName)) { Dbg.Err($"{stringName}:{defElement.LineNumber()}: Def name \"{defName}\" doesn't match regex pattern \"{DefNameValidator}\""); continue; } // Consume defName so we know it's not hanging around defElement.Attribute("defName").Remove(); // Check to see if we're abstract bool abstrct = false; { var abstractAttribute = defElement.Attribute("abstract"); if (abstractAttribute != null) { if (!bool.TryParse(abstractAttribute.Value, out abstrct)) { Dbg.Err($"{stringName}:{defElement.LineNumber()}: Error encountered when parsing abstract attribute"); } abstractAttribute.Remove(); } } // Get our parent info string parent = null; { var parentAttribute = defElement.Attribute("parent"); if (parentAttribute != null) { parent = parentAttribute.Value; parentAttribute.Remove(); } } // Register ourselves as an available parenting object { var identifier = Tuple.Create(typeHandle.GetDefRootType(), defName); if (potentialParents.ContainsKey(identifier)) { Dbg.Err($"{stringName}:{defElement.LineNumber()}: Def {identifier.Item1}:{identifier:Item2} redefined"); } else { potentialParents[identifier] = new Parent { xml = defElement, context = readerContext, parent = parent }; } } if (!abstrct) { // Not abstract, so create our instance var defInstance = (Def)Activator.CreateInstance(typeHandle); defInstance.DefName = defName; Database.Register(defInstance); if (parent == null) { // Non-parent objects are simple; we just handle them here in order to avoid unnecessary GC churn finishWork.Add(() => Serialization.ParseElement(defElement, typeHandle, defInstance, readerContext, isRootDef: true)); } else { // Add an inheritance resolution job; we'll take care of this soon inheritanceJobs.Add(new InheritanceJob { target = defInstance, xml = defElement, context = readerContext, parent = parent }); } } } } }
internal static object ParseString(string text, Type type, object model, string inputName, int lineNumber) { // Special case: Converter override // This is redundant if we're being called from ParseElement, but we aren't always. if (Converters.ContainsKey(type)) { object result; try { result = Converters[type].FromString(text, type, inputName, lineNumber); } catch (Exception e) { Dbg.Ex(e); if (type.IsValueType) { result = Activator.CreateInstance(type); } else { result = null; } } if (result != null && !type.IsAssignableFrom(result.GetType())) { Dbg.Err($"{inputName}:{lineNumber}: Converter {Converters[type].GetType()} for {type} returned unexpected type {result.GetType()}"); return(null); } return(result); } // Special case: defs if (typeof(Def).IsAssignableFrom(type)) { if (type.GetDefRootType() == null) { Dbg.Err($"{inputName}:{lineNumber}: Non-hierarchy defs cannot be used as references"); return(null); } if (text == "" || text == null) { // you reference nothing, you get the null return(null); } else { Def result = Database.Get(type, text); if (result == null) { Dbg.Err($"{inputName}:{lineNumber}: Couldn't find {type} named {text}"); } return(result); } } // Special case: types if (type == typeof(Type)) { if (text == "") { return(null); } return(UtilType.ParseDefFormatted(text, inputName, lineNumber)); } // Various non-composite-type special-cases if (text != "") { // If we've got text, treat us as an object of appropriate type try { return(TypeDescriptor.GetConverter(type).ConvertFromString(text)); } catch (System.Exception e) // I would normally not catch System.Exception, but TypeConverter is wrapping FormatException in an Exception for some reason { Dbg.Err($"{inputName}:{lineNumber}: {e.ToString()}"); return(model); } } else if (type == typeof(string)) { // If we don't have text, and we're a string, return "" return(""); } else { // If we don't have text, and we've fallen down to this point, that's an error (and return original value I guess) Dbg.Err($"{inputName}:{lineNumber}: Empty field provided for type {type}"); return(model); } }