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: decs if (typeof(Dec).IsAssignableFrom(type)) { if (type.GetDecRootType() == null) { Dbg.Err($"{inputName}:{lineNumber}: Non-hierarchy decs cannot be used as references"); return(null); } if (text == "" || text == null) { // you reference nothing, you get the null return(null); } else { Dec result = Database.Get(type, text); if (result == null) { // This feels very hardcoded, but these are also *by far* the most common errors I've seen, and I haven't come up with a better and more general solution if (text.Contains(" ")) { Dbg.Err($"{inputName}:{lineNumber}: Dec name \"{text}\" is not a valid identifier; consider removing spaces"); } else if (text.Contains("\"")) { Dbg.Err($"{inputName}:{lineNumber}: Dec name \"{text}\" is not a valid identifier; consider removing quotes"); } else if (!Parser.DecNameValidator.IsMatch(text)) { Dbg.Err($"{inputName}:{lineNumber}: Dec name \"{text}\" is not a valid identifier; dec identifiers must be valid C# identifiers"); } else { 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.ParseDecFormatted(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).ConvertFromInvariantString(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); } }
/// <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 != "Decs") { Dbg.Wrn($"{stringName}:{rootElement.LineNumber()}: Found root element with name \"{rootElement.Name.LocalName}\" when it should be \"Decs\""); } foreach (var decElement in rootElement.Elements()) { string typeName = decElement.Name.LocalName; Type typeHandle = UtilType.ParseDecFormatted(typeName, stringName, decElement.LineNumber()); if (typeHandle == null || !typeof(Dec).IsAssignableFrom(typeHandle)) { Dbg.Err($"{stringName}:{decElement.LineNumber()}: {typeName} is not a valid root Dec type"); continue; } if (decElement.Attribute("decName") == null) { Dbg.Err($"{stringName}:{decElement.LineNumber()}: No dec name provided"); continue; } string decName = decElement.Attribute("decName").Value; if (!DecNameValidator.IsMatch(decName)) { // This feels very hardcoded, but these are also *by far* the most common errors I've seen, and I haven't come up with a better and more general solution if (decName.Contains(" ")) { Dbg.Err($"{stringName}:{decElement.LineNumber()}: Dec name \"{decName}\" is not a valid identifier; consider removing spaces"); } else if (decName.Contains("\"")) { Dbg.Err($"{stringName}:{decElement.LineNumber()}: Dec name \"{decName}\" is not a valid identifier; consider removing quotes"); } else { Dbg.Err($"{stringName}:{decElement.LineNumber()}: Dec name \"{decName}\" is not a valid identifier; dec identifiers must be valid C# identifiers"); } continue; } // Consume decName so we know it's not hanging around decElement.Attribute("decName").Remove(); // Check to see if we're abstract bool abstrct = false; { var abstractAttribute = decElement.Attribute("abstract"); if (abstractAttribute != null) { if (!bool.TryParse(abstractAttribute.Value, out abstrct)) { Dbg.Err($"{stringName}:{decElement.LineNumber()}: Error encountered when parsing abstract attribute"); } abstractAttribute.Remove(); } } // Get our parent info string parent = null; { var parentAttribute = decElement.Attribute("parent"); if (parentAttribute != null) { parent = parentAttribute.Value; parentAttribute.Remove(); } } // Register ourselves as an available parenting object { var identifier = Tuple.Create(typeHandle.GetDecRootType(), decName); if (potentialParents.ContainsKey(identifier)) { Dbg.Err($"{stringName}:{decElement.LineNumber()}: Dec {identifier.Item1}:{identifier.Item2} defined twice"); } else { potentialParents[identifier] = new Parent { xml = decElement, context = readerContext, parent = parent }; } } if (!abstrct) { // Not an abstract dec instance, so create our instance var decInstance = (Dec)typeHandle.CreateInstanceSafe("dec", () => $"{stringName}:{decElement.LineNumber()}"); // Error reporting happens within CreateInstanceSafe; if we get null out, we just need to clean up elegantly if (decInstance != null) { decInstance.DecName = decName; Database.Register(decInstance); if (parent == null) { // Non-parent objects are simple; we just handle them here in order to avoid unnecessary GC churn finishWork.Add(() => Serialization.ParseElement(decElement, typeHandle, decInstance, readerContext, new Recorder.Context(), isRootDec: true)); } else { // Add an inheritance resolution job; we'll take care of this soon inheritanceJobs.Add(new InheritanceJob { target = decInstance, xml = decElement, context = readerContext, parent = parent }); } } } } } }