예제 #1
0
파일: Writer.cs 프로젝트: themotte/utils
        public string Write()
        {
            var doc = new XDocument();

            var record = new XElement("Defs");

            doc.Add(record);

            var writerContext = new WriterContext(false);

            foreach (var defObj in Database.List)
            {
                var defXml = Serialization.ComposeElement(defObj, defObj.GetType(), defObj.GetType().ComposeDefFormatted(), writerContext, isRootDef: true);
                defXml.Add(new XAttribute("defName", defObj.DefName));
                record.Add(defXml);
            }

            writerContext.DequeuePendingWrites();

            return(doc.ToString());
        }
예제 #2
0
        /// <summary>
        /// Returns a fully-formed XML document starting at an object.
        /// </summary>
        public static string Write <T>(T target, bool pretty = true)
        {
            var doc = new XDocument();

            var record = new XElement("Record");

            doc.Add(record);

            record.Add(new XElement("recordFormatVersion", 1));

            var refs = new XElement("refs");

            record.Add(refs);

            var writerContext = new WriterContext(true);

            var rootElement = Serialization.ComposeElement(target, target != null ? target.GetType() : typeof(T), "data", writerContext);

            record.Add(rootElement);

            // Handle all our pending writes
            writerContext.DequeuePendingWrites();

            // We now have a giant XML tree, potentially many thousands of nodes deep, where some nodes are references and some *should* be in the reference bank but aren't.
            // We need to do two things:
            // * Make all of our tagged references into actual references in the Refs section
            // * Tag anything deeper than a certain depth as a reference, then move it into the Refs section
            var depthTestsPending = new List <XElement>();

            depthTestsPending.Add(rootElement);

            // This is a loop between "write references" and "tag everything below a certain depth as needing to be turned into a reference".
            // We do this in a loop so we don't have to worry about ironically blowing our stack while making a change required to not blow our stack.
            while (true)
            {
                // Canonical ordering to provide some stability and ease-of-reading.
                foreach (var reference in writerContext.StripAndOutputReferences().OrderBy(kvp => kvp.Key))
                {
                    refs.Add(reference.Value);
                    depthTestsPending.Add(reference.Value);
                }

                bool found = false;
                for (int i = 0; i < depthTestsPending.Count; ++i)
                {
                    // Magic number should probably be configurable at some point
                    found |= writerContext.ProcessDepthLimitedReferences(depthTestsPending[i], 20);
                }
                depthTestsPending.Clear();

                if (!found)
                {
                    // No new depth-clobbering references found, just move on
                    break;
                }
            }

            if (refs.IsEmpty)
            {
                // strip out the refs 'cause it looks better that way :V
                refs.Remove();
            }

            return(doc.ToString());
        }
예제 #3
0
        internal static XElement ComposeElement(object value, Type fieldType, string label, WriterContext context, bool isRootDef = false)
        {
            var result = new XElement(label);

            // Do all our unreferencables first
            if (fieldType.IsPrimitive)
            {
                result.Add(new XText(value.ToString()));

                return(result);
            }

            if (value is string)
            {
                result.Add(new XText(value as string));

                return(result);
            }

            if (value is Type)
            {
                result.Add(new XText((value as Type).ComposeDefFormatted()));

                return(result);
            }

            if (!isRootDef && typeof(Def).IsAssignableFrom(fieldType))
            {
                // It is! Let's just get the def name and be done with it.
                if (value != null)
                {
                    var valueDef = value as Def;

                    if (valueDef != Database.Get(valueDef.GetType(), valueDef.DefName))
                    {
                        Dbg.Err("Referenced def {valueDef} no longer exists in the database; serializing an error value instead");
                        result.Add(new XText($"{valueDef.DefName}_DELETED"));
                    }
                    else
                    {
                        result.Add(new XText(valueDef.DefName));
                    }
                }

                // "No data" is defined as null for defs, so we just do that

                return(result);
            }

            // Everything after this represents "null" with an explicit XML tag, so let's just do that
            if (value == null)
            {
                result.SetAttributeValue("null", "true");
                return(result);
            }

            // Check to see if we should make this into a ref
            if (context.RecorderMode && !fieldType.IsValueType)
            {
                if (context.RegisterReference(value, result))
                {
                    // The ref system has set up the appropriate tagging, so we're done!
                    return(result);
                }

                // This is not a reference! (yet, at least). So keep on generating it.
            }

            // We'll drop through if we're in force-ref-resolve mode, or if we have something that needs conversion and is a struct (classes get turned into refs)

            // This is also where we need to start being concerned about types. If we have a type that isn't the expected type, tag it.
            if (value.GetType() != fieldType)
            {
                result.Add(new XAttribute("class", value.GetType().ComposeDefFormatted()));
            }

            if (fieldType.IsArray)
            {
                var list = value as Array;

                Type referencedType = fieldType.GetElementType();

                for (int i = 0; i < list.Length; ++i)
                {
                    result.Add(ComposeElement(list.GetValue(i), referencedType, "li", context));
                }

                return(result);
            }

            if (fieldType.IsGenericType && fieldType.GetGenericTypeDefinition() == typeof(List <>))
            {
                var list = value as IList;

                Type referencedType = fieldType.GetGenericArguments()[0];

                for (int i = 0; i < list.Count; ++i)
                {
                    result.Add(ComposeElement(list[i], referencedType, "li", context));
                }

                return(result);
            }

            if (fieldType.IsGenericType && fieldType.GetGenericTypeDefinition() == typeof(Dictionary <,>))
            {
                var dict = value as IDictionary;

                Type keyType   = fieldType.GetGenericArguments()[0];
                Type valueType = fieldType.GetGenericArguments()[1];

                // I really want some way to canonicalize this ordering
                IDictionaryEnumerator iterator = dict.GetEnumerator();
                while (iterator.MoveNext())
                {
                    // In theory, some dicts support inline format, not li format. Inline format is cleaner and smaller and we should be using it when possible.
                    // In practice, it's hard and I'm lazy and this always works, and we're not providing any guarantees about cleanliness of serialized output.
                    // Revisit this later when someone (possibly myself) really wants it improved.
                    var element = new XElement("li");
                    result.Add(element);

                    element.Add(ComposeElement(iterator.Key, keyType, "key", context));
                    element.Add(ComposeElement(iterator.Value, valueType, "value", context));
                }

                return(result);
            }

            if (typeof(IRecordable).IsAssignableFrom(fieldType))
            {
                var recordable = value as IRecordable;

                context.RegisterPendingWrite(() => recordable.Record(new RecorderWriter(result, context)));

                return(result);
            }

            {
                // Look for a converter; that's the only way to handle this before we fall back to reflection
                var converter = Serialization.Converters.TryGetValue(fieldType);
                if (converter != null)
                {
                    context.RegisterPendingWrite(() => converter.Record(value, fieldType, new RecorderWriter(result, context)));
                    return(result);
                }
            }

            if (context.RecorderMode)
            {
                Dbg.Err($"Couldn't find a composition method for type {fieldType}; do you need a Converter?");
                return(result);
            }

            // We absolutely should not be doing reflection when in recorder mode; that way lies madness.

            foreach (var field in value.GetType().GetFieldsFromHierarchy())
            {
                if (field.IsBackingField())
                {
                    continue;
                }

                if (field.GetCustomAttribute <IndexAttribute>() != null)
                {
                    // we don't save indices
                    continue;
                }

                if (field.GetCustomAttribute <NonSerializedAttribute>() != null)
                {
                    // we also don't save nonserialized
                    continue;
                }

                result.Add(ComposeElement(field.GetValue(value), field.FieldType, field.Name, context));
            }

            return(result);
        }