Example #1
0
        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);
            }
        }
Example #2
0
        /// <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
                                });
                            }
                        }
                    }
                }
            }
        }