Beispiel #1
0
        /// <summary>
        /// Parses the output of Write, generating an object and all its related serialized data.
        /// </summary>
        public static T Read <T>(string input, string stringName = "input")
        {
            XDocument doc;

            try
            {
                doc = XDocument.Parse(input, LoadOptions.SetLineInfo);
            }
            catch (System.Xml.XmlException e)
            {
                Dbg.Ex(e);
                return(default(T));
            }

            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 record = doc.Elements().First();

            if (record.Name.LocalName != "Record")
            {
                Dbg.Wrn($"{stringName}:{record.LineNumber()}: Found root element with name \"{record.Name.LocalName}\" when it should be \"Record\"");
            }

            var recordFormatVersion = record.ElementNamed("recordFormatVersion");

            if (recordFormatVersion == null)
            {
                Dbg.Err($"{stringName}:{record.LineNumber()}: Missing record format version, assuming the data is up-to-date");
            }
            else if (recordFormatVersion.GetText() != "1")
            {
                Dbg.Err($"{stringName}:{recordFormatVersion.LineNumber()}: Unknown record format version {recordFormatVersion.GetText()}, expected 1 or earlier");

                // I would rather not guess about this
                return(default(T));
            }

            var refs = record.ElementNamed("refs");

            var readerContext = new ReaderContext(stringName, true);

            if (refs != null)
            {
                // First, we need to make the instances for all the references, so they can be crosslinked appropriately
                foreach (var reference in refs.Elements())
                {
                    if (reference.Name.LocalName != "Ref")
                    {
                        Dbg.Wrn($"{stringName}:{reference.LineNumber()}: Reference element should be named 'Ref'");
                    }

                    var id = reference.Attribute("id")?.Value;
                    if (id == null)
                    {
                        Dbg.Err($"{stringName}:{reference.LineNumber()}: Missing reference ID");
                        continue;
                    }

                    var className = reference.Attribute("class")?.Value;
                    if (className == null)
                    {
                        Dbg.Err($"{stringName}:{reference.LineNumber()}: Missing reference class name");
                        continue;
                    }

                    var possibleType = (Type)Serialization.ParseString(className, typeof(Type), null, stringName, reference.LineNumber());
                    if (possibleType.IsValueType)
                    {
                        Dbg.Err($"{stringName}:{reference.LineNumber()}: Reference assigned type {possibleType}, which is a value type");
                        continue;
                    }

                    // Create a stub so other things can reference it later
                    readerContext.refs[id] = Activator.CreateInstance(possibleType);
                    if (readerContext.refs[id] == null)
                    {
                        // This is difficult to test; there are very few things that can get CreateInstance to return null, and right now the type system doesn't support them (int? for example)
                        Dbg.Err($"{stringName}:{reference.LineNumber()}: Reference of type {possibleType} was not properly created; this will cause issues");
                        continue;
                    }
                }

                // Now that all the refs exist, we can run through them again and actually parse them
                foreach (var reference in refs.Elements())
                {
                    var id = reference.Attribute("id")?.Value;
                    if (id == null)
                    {
                        // Just skip it, we don't have anything useful we can do here
                        continue;
                    }

                    // The serialization routines don't know how to deal with this, so we'll remove it now
                    reference.Attribute("id").Remove();

                    var refInstance = readerContext.refs.TryGetValue(id);
                    if (refInstance == null)
                    {
                        // We failed to parse this for some reason, so just skip it now
                        continue;
                    }

                    // Do our actual parsing
                    var refInstanceOutput = Serialization.ParseElement(reference, refInstance.GetType(), refInstance, readerContext, hasReferenceId: true);

                    if (refInstance != refInstanceOutput)
                    {
                        Dbg.Err($"{stringName}:{reference.LineNumber()}: Something really bizarre has happened and we got the wrong object back. Things are probably irrevocably broken. Please report this as a bug in Def.");
                        continue;
                    }
                }
            }

            var data = record.ElementNamed("data");

            if (data == null)
            {
                Dbg.Err($"{stringName}:{record.LineNumber()}: No data element provided. This is not very recoverable.");

                return(default(T));
            }

            // And now, we can finally parse our actual root element!
            // (which accounts for a tiny percentage of things that need to be parsed)
            return((T)Serialization.ParseElement(data, typeof(T), null, readerContext));
        }
Beispiel #2
0
        /// <summary>
        /// Finish all parsing.
        /// </summary>
        public void Finish()
        {
            if (s_Status != Status.Accumulating)
            {
                Dbg.Err($"Finishing while the world is in {s_Status} state; should be {Status.Accumulating} state");
            }
            s_Status = Status.Processing;

            // Resolve all our inheritance jobs
            foreach (var work in inheritanceJobs)
            {
                // These are the actions we need to perform; we actually have to resolve these backwards (it makes their construction a little easier)
                // The final parse is listed first, then all the children up to the final point
                var actions = new List <Action>();

                actions.Add(() => Serialization.ParseElement(work.xml, work.target.GetType(), work.target, work.context, isRootDef: true));

                string        currentDefName = work.target.DefName;
                XElement      currentXml     = work.xml;
                ReaderContext currentContext = work.context;

                string parentDefName = work.parent;
                while (parentDefName != null)
                {
                    var parentData = potentialParents.TryGetValue(Tuple.Create(work.target.GetType().GetDefRootType(), parentDefName));

                    // This is a struct for the sake of performance, so child itself won't be null
                    if (parentData.xml == null)
                    {
                        Dbg.Err($"{currentContext.sourceName}:{currentXml.LineNumber()}: Def {currentDefName} is attempting to use parent {parentDefName}, but no such def exists");

                        // Not much more we can do here.
                        break;
                    }

                    actions.Add(() => Serialization.ParseElement(parentData.xml, work.target.GetType(), work.target, parentData.context, isRootDef: true));

                    currentDefName = parentDefName;
                    currentXml     = parentData.xml;
                    currentContext = parentData.context;

                    parentDefName = parentData.parent;
                }

                finishWork.Add(() =>
                {
                    for (int i = actions.Count - 1; i >= 0; --i)
                    {
                        actions[i]();
                    }
                });
            }

            foreach (var work in finishWork)
            {
                work();
            }

            if (s_Status != Status.Processing)
            {
                Dbg.Err($"Distributing while the world is in {s_Status} state; should be {Status.Processing} state");
            }
            s_Status = Status.Distributing;

            staticReferencesRegistering.Clear();
            staticReferencesRegistering.UnionWith(staticReferences);
            foreach (var stat in staticReferences)
            {
                StaticReferencesAttribute.StaticReferencesFilled.Add(stat);

                foreach (var field in stat.GetFields(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static))
                {
                    var def = Database.Get(field.FieldType, field.Name);
                    if (def == null)
                    {
                        Dbg.Err($"Failed to find {field.FieldType} named {field.Name}");
                    }
                    else if (!field.FieldType.IsAssignableFrom(def.GetType()))
                    {
                        Dbg.Err($"Static reference {field.FieldType} {stat}.{field.Name} is not compatible with {def.GetType()} {def}");
                        field.SetValue(null, null); // this is unnecessary, but it does kick the static constructor just in case we wouldn't do it otherwise
                    }
                    else
                    {
                        field.SetValue(null, def);
                    }
                }

                if (!staticReferencesRegistered.Contains(stat))
                {
                    Dbg.Err($"Failed to properly register {stat}; you may be missing a call to Def.StaticReferences.Initialized() in its static constructor");
                }
            }

            if (s_Status != Status.Distributing)
            {
                Dbg.Err($"Finalizing while the world is in {s_Status} state; should be {Status.Distributing} state");
            }
            s_Status = Status.Finalizing;

            foreach (var def in Database.List)
            {
                try
                {
                    def.ConfigErrors(err => Dbg.Err($"{def.GetType()} {def}: {err}"));
                }
                catch (Exception e)
                {
                    Dbg.Ex(e);
                }
            }

            foreach (var def in Database.List)
            {
                try
                {
                    def.PostLoad(err => Dbg.Err($"{def.GetType()} {def}: {err}"));
                }
                catch (Exception e)
                {
                    Dbg.Ex(e);
                }
            }

            if (s_Status != Status.Finalizing)
            {
                Dbg.Err($"Completing while the world is in {s_Status} state; should be {Status.Finalizing} state");
            }
            s_Status = Status.Finished;
        }
Beispiel #3
0
        internal static object ParseElement(XElement element, Type type, object model, ReaderContext context, bool isRootDef = false, bool hasReferenceId = false)
        {
            // The first thing we do is parse all our attributes. This is because we want to verify that there are no attributes being ignored.
            // Don't return anything until we do our element.HasAtttributes check!

            // Figure out our intended type, if it's been overridden
            if (element.Attribute("class") != null)
            {
                var className    = element.Attribute("class").Value;
                var possibleType = (Type)ParseString(className, typeof(Type), null, context.sourceName, element.LineNumber());
                if (!type.IsAssignableFrom(possibleType))
                {
                    Dbg.Err($"{context.sourceName}:{element.LineNumber()}: Explicit type {className} cannot be assigned to expected type {type}");
                }
                else if (model != null && model.GetType() != possibleType)
                {
                    Dbg.Err($"{context.sourceName}:{element.LineNumber()}: Explicit type {className} does not match already-provided instance {type}");
                }
                else
                {
                    type = possibleType;
                }

                element.Attribute("class").Remove();
            }

            bool   shouldBeNull = bool.Parse(element.ConsumeAttribute("null") ?? "false");
            string refId        = element.ConsumeAttribute("ref");

            if (shouldBeNull && refId != null)
            {
                Dbg.Err($"{context.sourceName}:{element.LineNumber()}: Element cannot be both null and a reference at the same time");

                // There's no good answer here, but we're sticking with the null because it feels like an error-handling path that the user is more likely to properly support.
                refId = null;
            }

            // See if we just want to return null
            if (shouldBeNull)
            {
                // No remaining attributes are allowed in nulls
                if (element.HasAttributes)
                {
                    Dbg.Err($"{context.sourceName}:{element.LineNumber()}: Has unconsumed attributes");
                }

                // okay
                return(null);

                // Note: It may seem wrong that we can return null along with a non-null model.
                // The problem is that this is meant to be able to override defaults. If the default if an object, explicitly setting it to null *should* clear the object out.
                // If we actually need a specific object to be returned, for whatever reason, the caller has to do the comparison.
            }

            // See if we can get a ref out of it
            if (refId != null)
            {
                // No remaining attributes are allowed in refs
                if (element.HasAttributes)
                {
                    Dbg.Err($"{context.sourceName}:{element.LineNumber()}: Has unconsumed attributes");
                }

                if (context == null)
                {
                    Dbg.Err($"{context.sourceName}:{element.LineNumber()}: Found a reference object outside of record-reader mode");
                    return(model);
                }

                if (!context.refs.ContainsKey(refId))
                {
                    Dbg.Err($"{context.sourceName}:{element.LineNumber()}: Found a reference object {refId} without a valid reference mapping");
                    return(model);
                }

                object refObject = context.refs[refId];
                if (!type.IsAssignableFrom(refObject.GetType()))
                {
                    Dbg.Err($"{context.sourceName}:{element.LineNumber()}: Reference object {refId} is of type {refObject.GetType()}, which cannot be converted to expected type {type}");
                    return(model);
                }

                return(refObject);
            }

            // Converters may do their own processing, so we'll just defer off to them now; hell, you can even have both elements and text, if that's your jam
            if (Converters.ContainsKey(type))
            {
                // context might be null; that's OK at the moment
                object result;

                try
                {
                    result = Converters[type].Record(model, type, new RecorderReader(element, context));
                }
                catch (Exception e)
                {
                    Dbg.Ex(e);

                    if (model != null)
                    {
                        result = model;
                    }
                    else if (type.IsValueType)
                    {
                        result = Activator.CreateInstance(type);
                    }
                    else
                    {
                        result = null;
                    }
                }

                // This is an important check if we have a referenced type, because if we've changed the result, references won't link up to it properly.
                // Outside referenced types, it doesn't matter - we want to give people as much control over modification as possible.
                if (model != null && hasReferenceId && model != result)
                {
                    Dbg.Err($"{context.sourceName}:{element.LineNumber()}: Converter {Converters[type].GetType()} for {type} ignored the model {model} while reading a referenced object; this may cause lost data");
                    return(result);
                }

                if (result != null && !type.IsAssignableFrom(result.GetType()))
                {
                    Dbg.Err($"{context.sourceName}:{element.LineNumber()}: Converter {Converters[type].GetType()} for {type} returned unexpected type {result.GetType()}");
                    return(null);
                }

                return(result);
            }

            // After this point we won't be using a converter in any way, we'll be requiring native Def types (as native as it gets, at least)

            bool hasElements = element.Elements().Any();
            bool hasText     = element.Nodes().OfType <XText>().Any();

            if (typeof(Def).IsAssignableFrom(type) && hasElements && !isRootDef)
            {
                Dbg.Err($"{context.sourceName}:{element.LineNumber()}: Inline def definitions are not currently supported");
                return(null);
            }

            // Special case: IRecordables
            if (typeof(IRecordable).IsAssignableFrom(type))
            {
                var recordable = (IRecordable)(model ?? Activator.CreateInstance(type));

                recordable.Record(new RecorderReader(element, context));

                // TODO: support indices if this is within the Def system?

                return(recordable);
            }

            // No remaining attributes are allowed past this point!
            if (element.HasAttributes)
            {
                Dbg.Err($"{context.sourceName}:{element.LineNumber()}: Has unconsumed attributes");
            }

            // All our standard text-using options
            if (hasText ||
                (typeof(Def).IsAssignableFrom(type) && !isRootDef) ||
                type == typeof(Type) ||
                type == typeof(string) ||
                type.IsPrimitive)
            {
                if (hasElements)
                {
                    Dbg.Err($"{context.sourceName}:{element.LineNumber()}: Elements are not valid when parsing {type}");
                }

                return(ParseString(element.GetText(), type, model, context.sourceName, element.LineNumber()));
            }

            // Nothing past this point even supports text, so let's just get angry and break stuff.
            if (hasText)
            {
                Dbg.Err($"{context.sourceName}:{element.LineNumber()}: Text detected in a situation where it is invalid; will be ignored");
            }

            // Special case: Lists
            if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(List <>))
            {
                // List<> handling
                Type referencedType = type.GetGenericArguments()[0];

                var list = (IList)(model ?? Activator.CreateInstance(type));

                // If you have a default list, but specify it in XML, we assume this is a full override. Clear the original list.
                list.Clear();

                foreach (var fieldElement in element.Elements())
                {
                    if (fieldElement.Name.LocalName != "li")
                    {
                        Dbg.Err($"{context.sourceName}:{fieldElement.LineNumber()}: Tag should be <li>, is <{fieldElement.Name.LocalName}>");
                    }

                    list.Add(ParseElement(fieldElement, referencedType, null, context));
                }

                return(list);
            }

            // Special case: Arrays
            if (type.IsArray)
            {
                Type referencedType = type.GetElementType();

                var elements = element.Elements().ToArray();

                // We don't bother falling back on model here; we probably need to recreate it anyway with the right length
                var array = (Array)Activator.CreateInstance(type, new object[] { elements.Length });

                for (int i = 0; i < elements.Length; ++i)
                {
                    var fieldElement = elements[i];
                    if (fieldElement.Name.LocalName != "li")
                    {
                        Dbg.Err($"{context.sourceName}:{fieldElement.LineNumber()}: Tag should be <li>, is <{fieldElement.Name.LocalName}>");
                    }

                    array.SetValue(ParseElement(fieldElement, referencedType, null, context), i);
                }

                return(array);
            }

            // Special case: Dictionaries
            if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Dictionary <,>))
            {
                // Dictionary<> handling
                Type keyType   = type.GetGenericArguments()[0];
                Type valueType = type.GetGenericArguments()[1];

                var dict = (IDictionary)(model ?? Activator.CreateInstance(type));

                // If you have a default dict, but specify it in XML, we assume this is a full override. Clear the original dict.
                dict.Clear();

                foreach (var fieldElement in element.Elements())
                {
                    if (fieldElement.Name.LocalName == "li")
                    {
                        // Treat this like a key/value pair
                        var keyNode   = fieldElement.ElementNamed("key");
                        var valueNode = fieldElement.ElementNamed("value");

                        if (keyNode == null)
                        {
                            Dbg.Err($"{context.sourceName}:{fieldElement.LineNumber()}: Dictionary includes li tag without a key");
                            continue;
                        }

                        if (valueNode == null)
                        {
                            Dbg.Err($"{context.sourceName}:{fieldElement.LineNumber()}: Dictionary includes li tag without a value");
                            continue;
                        }

                        var key = ParseElement(keyNode, keyType, null, context);

                        if (dict.Contains(key))
                        {
                            Dbg.Err($"{context.sourceName}:{fieldElement.LineNumber()}: Dictionary includes duplicate key {key.ToString()}");
                        }

                        dict[key] = ParseElement(valueNode, valueType, null, context);
                    }
                    else
                    {
                        var key = ParseString(fieldElement.Name.LocalName, keyType, null, context.sourceName, fieldElement.LineNumber());

                        if (dict.Contains(key))
                        {
                            Dbg.Err($"{context.sourceName}:{fieldElement.LineNumber()}: Dictionary includes duplicate key {fieldElement.Name.LocalName}");
                        }

                        dict[key] = ParseElement(fieldElement, valueType, null, context);
                    }
                }

                return(dict);
            }

            // At this point, we're either a class or a struct, and we need to do the reflection thing

            // If we have refs, something has gone wrong; we should never be doing reflection inside a Record system.
            // This is a really ad-hoc way of testing this and should be fixed.
            // One big problem here is that I'm OK with security vulnerabilities in def xmls. Those are either supplied by the developer or by mod authors who are intended to have full code support anyway.
            // I'm less OK with security vulnerabilities in save files. Nobody expects a savefile can compromise their system.
            // And the full reflection system is probably impossible to secure, whereas the Record system should be secureable.
            if (context.RecorderMode)
            {
                Dbg.Err($"{context.sourceName}:{element.LineNumber()}: Falling back to reflection within a Record system; this is currently not allowed for security reasons");
                return(model);
            }

            // If we haven't been given a template class from our parent, go ahead and init to defaults
            if (model == null)
            {
                model = Activator.CreateInstance(type);
            }

            var setFields = new HashSet <string>();

            foreach (var fieldElement in element.Elements())
            {
                // Check for fields that have been set multiple times
                string fieldName = fieldElement.Name.LocalName;
                if (setFields.Contains(fieldName))
                {
                    Dbg.Err($"{context.sourceName}:{element.LineNumber()}: Duplicate field {fieldName}");
                    // Just allow us to fall through; it's an error, but one with a reasonably obvious handling mechanism
                }
                setFields.Add(fieldName);

                var fieldInfo = type.GetFieldFromHierarchy(fieldName);
                if (fieldInfo == null)
                {
                    // Try to find a close match, if we can, just for a better error message
                    string match = null;
                    string canonicalFieldName = Util.LooseMatchCanonicalize(fieldName);

                    foreach (var testField in type.GetFieldsFromHierarchy())
                    {
                        if (Util.LooseMatchCanonicalize(testField.Name) == canonicalFieldName)
                        {
                            match = testField.Name;

                            // We could in theory do something overly clever where we try to find the best name, but I really don't care that much; this is meant as a quick suggestion, not an ironclad solution.
                            break;
                        }
                    }

                    if (match != null)
                    {
                        Dbg.Err($"{context.sourceName}:{element.LineNumber()}: Field {fieldName} does not exist in type {type}; did you mean {match}?");
                    }
                    else
                    {
                        Dbg.Err($"{context.sourceName}:{element.LineNumber()}: Field {fieldName} does not exist in type {type}");
                    }

                    continue;
                }

                if (fieldInfo.GetCustomAttribute <IndexAttribute>() != null)
                {
                    Dbg.Err($"{context.sourceName}:{element.LineNumber()}: Attempting to set index field {fieldName}; these are generated by the def system");
                    continue;
                }

                if (fieldInfo.GetCustomAttribute <NonSerializedAttribute>() != null)
                {
                    Dbg.Err($"{context.sourceName}:{element.LineNumber()}: Attempting to set nonserialized field {fieldName}");
                    continue;
                }

                // Check for fields we're not allowed to set
                if (UtilReflection.ReflectionSetForbidden(fieldInfo))
                {
                    Dbg.Err($"{context.sourceName}:{element.LineNumber()}: Field {fieldName} is not allowed to be set through reflection");
                    continue;
                }

                fieldInfo.SetValue(model, ParseElement(fieldElement, fieldInfo.FieldType, fieldInfo.GetValue(model), context));
            }


            // Set up our index fields; this has to happen last in case we're a struct
            Index.Register(ref model);

            return(model);
        }
Beispiel #4
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 != "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
                            });
                        }
                    }
                }
            }
        }
Beispiel #5
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: 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);
            }
        }