Exemple #1
0
 private void RaiseItemsMovedEvent(ListMergerSide from, ListMergerSide to, ListMergeMode action, object[] items)
 {
     OnItemMoved(from, to, action, items);
     if (ItemsMoved != null)
     {
         ItemsMoved(this, from, to, action, items);
     }
 }
Exemple #2
0
 protected virtual void OnItemMoved(ListMergerSide from, ListMergerSide to, ListMergeMode action, object item)
 {
 }
Exemple #3
0
        /// <summary>
        /// Modifies the main list to combine it with the elements of the secondary list.
        /// The method used to combine is set using the mode parameter.
        /// The secondary list is not modified.
        /// </summary>
        public static void Combine(IList main, IList secondary, ListMergeMode mode)
        {
            if (main == null || secondary == null || secondary.Count == 0)
            {
                return;
            }

            if ((main.IsFixedSize || main.IsReadOnly) && mode != ListMergeMode.Replace)
            {
                XmlController.Log("[ERROR] Main list is fixed size or read only.");
                return;
            }

            switch (mode)
            {
            case ListMergeMode.Replace:

                // Clear list and add all values from other list.
                main.Clear();
                foreach (var value in secondary)
                {
                    main.Add(value);
                }

                break;

            case ListMergeMode.Append:

                // Simply add all values from secondary to primary.
                foreach (var value in secondary)
                {
                    main.Add(value);
                }

                break;

            case ListMergeMode.Merge:

                // Add all values from other list, if they are not already in the list.
                foreach (var value in secondary)
                {
                    if (!main.Contains(value))
                    {
                        temp.Add(value);
                    }
                }
                foreach (var value in temp)
                {
                    main.Add(value);
                }

                break;

            case ListMergeMode.MergeReplace:
                // Add all values from the other list, if they are not already in the list.
                // If they already are in the list, replace it with the new value.
                // Useful for custom classes that override .equals()
                foreach (var value in secondary)
                {     // TODO left off here, needs to work like Merge where items are inserted.
                    int index = main.IndexOf(value);
                    if (index != -1)
                    {
                        temp.Add(value);
                    }
                }

                foreach (var value in secondary)
                {
                    int index = main.IndexOf(value);
                    if (index == -1)
                    {
                        main.Add(value);
                    }
                    else
                    {
                        main.RemoveAt(index);
                        main.Insert(index, value);
                    }
                }

                break;

            case ListMergeMode.Subtract:

                // Removes values from main if they are in secondary.
                foreach (var value in secondary)
                {
                    if (main.Contains(value))
                    {
                        main.Remove(value);
                    }
                }

                break;

            default:
                XmlController.Log($"[ERROR] {mode} is not implemented.");
                break;
            }

            temp.Clear();
            temp2.Clear();
        }
Exemple #4
0
        public T Deserialize <T>(string xml, T toFill) where T : class
        {
            doc.LoadXml(xml);

            Stopwatch watch = new Stopwatch();

            watch.Start();

            FieldWrapper currentField = default;
            object       rootObject   = null;
            object       parentObject = null;

            Type rootType    = toFill == null ? typeof(T) : toFill.GetType();
            T    returnValue = (T)CreateAndPopulate(toFill, doc.FirstContentNode(), rootType);

            watch.Stop();
            //OnLog?.Invoke($"Took {watch.Elapsed.TotalMilliseconds:F2} ms");

            return(returnValue);

            object CreateAndPopulate(object existing, XmlNode rootNode, Type type)
            {
                bool   isRootAndFilling = rootObject == null && toFill != null;
                string customClassName  = rootNode.TryGetAttribute("class");

                if (customClassName != null)
                {
                    customClassName = customClassName.Trim();
                    var newType = TypeResolver.Resolve(customClassName);
                    if (newType == null)
                    {
                        OnLog?.Invoke($"[ERROR] Node {rootNode.GetXPath()}: Could not find custom class '{customClassName}' for node {rootNode.Name}. Node will be ignored.");
                        return(null);
                    }

                    bool currentTypeCanGoIntoRoot = isRootAndFilling && newType.IsAssignableFrom(type);

                    if (!type.IsAssignableFrom(newType) && !currentTypeCanGoIntoRoot)
                    {
                        string problem = type.IsInterface ? "does not implement interface" : "is not a subclass of";
                        OnLog?.Invoke($"[ERROR] Node {rootNode.GetXPath()}: {newType.FullName} {problem} {type.FullName}. Node will be ignored.");
                        return(null);
                    }

                    type = newType;
                }

                if (type.IsRealNullable())
                {
                    Type replacement = Nullable.GetUnderlyingType(type);
                    //OnLog?.Invoke($"{type.Name} is a nullable type, using {replacement.Name} instead.");
                    type = replacement;
                }

                var  loader       = GetRootTypeSerializer(type);
                bool isBasic      = loader != null;
                bool isArrayType  = IsArrayType(type);
                bool isListType   = IsListType(type);
                bool isDictType   = IsDictionaryType(type);
                bool isEnumType   = IsEnumType(type);
                bool tryUseCustom = !isRootAndFilling && !isBasic && !isArrayType && !isListType && !isDictType && !isEnumType && !type.IsPrimitive;

                object DoCustom(Type baseType)
                {
                    var custom = customResolvers[baseType];

                    return(custom.Invoke(new CustomResolverArgs()
                    {
                        XmlNode = rootNode,
                        ExistingObject = existing,
                        Field = currentField,
                        RootObject = rootObject,
                        ParentObject = parentObject
                    }));
                }

                if (tryUseCustom)
                {
                    if (customResolverTypeMap.TryGetValue(type, out var foundBaseType))
                    {
                        if (foundBaseType != null)
                        {
                            return(DoCustom(foundBaseType));
                        }
                    }
                    else
                    {
                        foreach (var ct in customResolvers)
                        {
                            var crType = ct.Key;
                            if (crType.IsAssignableFrom(type))
                            {
                                // Found it!
                                customResolverTypeMap.Add(type, crType);
                                return(DoCustom(crType));
                            }
                        }
                    }
                }

                if ((type.IsAbstract || type.IsInterface) && !isRootAndFilling && !isBasic)
                {
                    string problem = type.IsInterface ? "an interface" : "an abstract class";
                    OnLog?.Invoke($"[ERROR] Node {rootNode.GetXPath()}: {type.FullName} is {problem} and so cannot be instantiated. Please use the class='typeName' attribute to specify a concrete class. Node will be ignored.");
                    return(null);
                }

                if (isArrayType)
                {
                    // Create the array.
                    int arrayLength = 0;
                    for (int i = 0; i < rootNode.ChildNodes.Count; i++)
                    {
                        var node = rootNode.ChildNodes.Item(i);
                        if (node.NodeType == XmlNodeType.Element)
                        {
                            arrayLength++;
                        }
                    }

                    Array created   = CreateInstance(type, arrayLength) as Array;
                    Type  arrayType = type.GetElementType();
                    for (int i = 0; i < created.Length; i++)
                    {
                        var    node       = rootNode.ChildNodes.Item(i);
                        object atPosition = CreateAndPopulate(null, node, arrayType);

                        created.SetValue(atPosition, i);
                    }

                    string attr = rootNode.TryGetAttribute("mode");
                    if (attr != null)
                    {
                        OnLog?.Invoke($"[ERROR] Node {rootNode.GetXPath()}: Arrays cannot use merge modes. To use merge modes, change it to a List<{arrayType.Name}>.");
                    }

                    return(created);
                }

                if (isListType)
                {
                    if (!type.IsGenericType)
                    {
                        OnLog?.Invoke($"[ERROR] Node {rootNode.GetXPath()}: Non-generic list type {type.Name} is not supported.");
                        return(null);
                    }

                    IList old = existing as IList;

                    Type  listType        = type.GetGenericArguments()[0];
                    IList created         = (IList)CreateInstance(type);
                    bool  allowNullValues = listType.IsNullable();
                    for (int i = 0; i < rootNode.ChildNodes.Count; i++)
                    {
                        var node = rootNode.ChildNodes.Item(i);
                        if (node.NodeType == XmlNodeType.Element)
                        {
                            object atPosition = CreateAndPopulate(null, node, listType);

                            if (atPosition == null && !allowNullValues)
                            {
                                OnLog?.Invoke($"[ERROR] List {rootNode.GetXPath()} has a null value. This is not valid because the list type is {listType.Name}.");
                                continue;
                            }
                            created.Add(atPosition);
                        }
                    }

                    if (old == null)
                    {
                        return(created);
                    }

                    ListMergeMode mode = rootNode.TryParseAttributeEnum <ListMergeMode>("mode") ?? ListMergeMode.MergeReplace;
                    if ((old.IsFixedSize || old.IsReadOnly) && mode != ListMergeMode.Replace)
                    {
                        OnLog?.Invoke($"[ERROR] Node {rootNode.GetXPath()}: This List<{listType.Name}> uses merge mode {mode}, but the list instance is read-only. New list will replace old values.");
                        mode = ListMergeMode.Replace;
                    }

                    ListMergeUtils.Combine(old, created, mode);

                    return(old);
                }

                if (isDictType)
                {
                    if (!type.IsGenericType)
                    {
                        OnLog?.Invoke($"[ERROR] Node {rootNode.GetXPath()}: Non-generic dictionary type {type.Name} is not supported.");
                        return(null);
                    }
                    Type[] dictParams = type.GetGenericArguments();
                    Type   keyType    = dictParams[0];
                    Type   valueType  = dictParams[1];

                    IDictionary created     = (IDictionary)CreateInstance(type);
                    bool?       attrCompact = rootNode.TryParseAttributeBool("compact");
                    bool        useCompact  = keyType == typeof(string) && (attrCompact == null || attrCompact.Value);
                    if (keyType != typeof(string) && attrCompact != null && attrCompact.Value)
                    {
                        OnLog?.Invoke($"[ERROR] Node {rootNode.GetXPath()} has compact='true', but the dictionary has keys of type {keyType.Name}. They must be Strings to use compact mode.");
                    }
                    bool allowNullValues = valueType.IsNullable();
                    if (useCompact)
                    {
                        // The key is an attribute, the value is the node value.
                        for (int i = 0; i < rootNode.ChildNodes.Count; i++)
                        {
                            var node = rootNode.ChildNodes.Item(i);
                            if (node.NodeType != XmlNodeType.Element)
                            {
                                continue;
                            }

                            string key = node.TryGetAttribute("key");
                            if (key == null)
                            {
                                OnLog?.Invoke($"[ERROR] Dictionary {rootNode.GetXPath()} has element at index {i} that does not have a key attribute, such as key='hello'. Compact mode is enabled. Use compact='false' to disable compact mode on this dictionary.");
                                continue;
                            }
                            if (created.Contains(key))
                            {
                                OnLog?.Invoke($"[ERROR] Duplicate key in dictionary {rootNode.GetXPath()}: '{key}'");
                                continue;
                            }
                            object value = CreateAndPopulate(null, node, valueType);

                            if (value == null && !allowNullValues)
                            {
                                OnLog?.Invoke($"[ERROR] Dictionary {rootNode.GetXPath()} has a null value. This is not valid because the value type is {valueType.Name}.");
                                continue;
                            }

                            created.Add(key, value);
                        }
                    }
                    else
                    {
                        // The key is a node with the name K, a value is a node with the name V.
                        // K and V's Should be in sequence and should be in pairs.
                        // The key is an attribute, the value is the node value.
                        bool   expectKey = true;
                        int    index     = -1;
                        object lastKey   = null;
                        for (int i = 0; i < rootNode.ChildNodes.Count; i++)
                        {
                            var node = rootNode.ChildNodes.Item(i);
                            if (node.NodeType != XmlNodeType.Element)
                            {
                                continue;
                            }

                            index++;
                            string nodeName = node.Name.Trim().ToLower();
                            bool   isKey    = nodeName == "k" || nodeName == "key";
                            bool   isValue  = nodeName == "v" || nodeName == "value";

                            if (!isKey && !isValue)
                            {
                                OnLog?.Invoke($"[ERROR] Dictionary {rootNode.Name} has an item at index {index} called '{node.Name}'. Dictionary items, when not in compact mode, should only be named either K or V. Value will be assumed to be a {(expectKey ? "key" : "value")}.");
                                if (expectKey)
                                {
                                    isKey = true;
                                }
                                else
                                {
                                    isValue = true;
                                }
                            }

                            if (isKey && !expectKey)
                            {
                                OnLog?.Invoke($"[ERROR] Dictionary {rootNode.Name} has two keys in a row! Item order must be key, value, key, value etc.");
                                continue;
                            }
                            if (!isKey && expectKey)
                            {
                                OnLog?.Invoke($"[ERROR] Dictionary {rootNode.Name} has two values in a row! Item order must be key, value, key, value etc.");
                                continue;
                            }

                            if (isKey)
                            {
                                lastKey   = CreateAndPopulate(null, node, keyType);
                                expectKey = false;
                            }
                            else
                            {
                                expectKey = true;
                                if (created.Contains(lastKey))
                                {
                                    OnLog?.Invoke($"[ERROR] Duplicate key in dictionary {rootNode.GetXPath()}: '{lastKey}'");
                                    lastKey = null;
                                    continue;
                                }
                                object value = CreateAndPopulate(null, node, valueType);
                                if (value == null && !allowNullValues)
                                {
                                    OnLog?.Invoke($"[ERROR] Dictionary {rootNode.GetXPath()} has a null value. This is not valid because the value type is {valueType.Name}.");
                                    continue;
                                }
                                created.Add(lastKey, value);
                                lastKey = null;
                            }
                        }
                    }

                    if (!(existing is IDictionary oldDict))
                    {
                        return(created);
                    }

                    ListMergeMode mode = rootNode.TryParseAttributeEnum <ListMergeMode>("mode") ?? ListMergeMode.MergeReplace;
                    if ((oldDict.IsFixedSize || oldDict.IsReadOnly) && mode != ListMergeMode.Replace)
                    {
                        OnLog?.Invoke($"[ERROR] Node {rootNode.Name}: This Dictionary<{keyType.Name}, {valueType.Name}> uses merge mode {mode}, but the dictioary instance is read-only. New dictionary will replace old values.");
                        mode = ListMergeMode.Replace;
                    }

                    ListMergeUtils.Combine(oldDict, created, mode);

                    return(oldDict);
                }

                if (isEnumType)
                {
                    string textContent = rootNode.InnerText;
                    try
                    {
                        var parsedEnum = Enum.Parse(type, textContent, true);
                        return(parsedEnum);
                    }
                    catch
                    {
                        OnLog?.Invoke($"Failed to parse '{textContent}' as enum {type.Name}");
                    }
                }

                if (isBasic)
                {
                    var firstChild = rootNode.FirstContentNode();
                    if (firstChild == null)
                    {
                        OnLog?.Invoke($"[ERROR] Null value (empty tag) in node {rootNode.GetXPath()}. Expected a {loader.TargetType.Name}.");
                        return(null);
                    }

                    try
                    {
                        var fromLoader = loader.Deserialize(firstChild);
                        return(fromLoader);
                    }
                    catch (Exception e)
                    {
                        OnLog?.Invoke($"[ERROR] Exception deserializing value '{firstChild.InnerText}' as a {loader.TargetType.Name} using loader {loader.GetType().Name} for node {firstChild.GetXPath()}:\n{e}");
                        return(null);
                    }
                }
                else
                {
                    // Create new object (class or struct)
                    bool didCreate = existing == null;
                    var  created   = existing ?? CreateInstance(type);
                    if (didCreate && created is IXmlAware aware)
                    {
                        aware.OnCreateFromNode(rootNode);
                    }
                    rootObject ??= created;
                    parentObject = created;

                    // Get all child nodes in this node.
                    var children = rootNode.ChildNodes;
                    for (int i = 0; i < children.Count; i++)
                    {
                        var node = children.Item(i);

                        //OnLog?.Invoke($"[{node.NodeType}] {node.Name}: {node.Value}");
                        if (node.NodeType != XmlNodeType.Element && node.NodeType != XmlNodeType.Comment)
                        {
                            // You should not be here... Most likely a text. However, the xml loaded correctly, so it might be safe to ignore with just a warning.
                            OnLog?.Invoke($"Unexpected node of type '{node.NodeType}' at {node.GetXPath()}. Content: '{node.Value?.Trim()}'. Please remove.");
                        }
                        if (node.NodeType != XmlNodeType.Element)
                        {
                            continue;
                        }

                        string fieldName = node.Name;
                        var    field     = GetField(fieldName, type);
                        if (!field.IsValid)
                        {
                            OnLog?.Invoke($"Error: Failed to find field '{type.FullName}.{fieldName}'");
                            continue;
                        }

                        Type childType = field.FieldType;

                        currentField = field;
                        var childObj = CreateAndPopulate(ReadField(field, created), node, childType);
                        currentField = FieldWrapper.Invalid;

                        WriteField(field, created, childObj, node);
                    }

                    parentObject = null;
                    return(created);
                }
            }
        }
Exemple #5
0
        public static void Combine(IDictionary main, IDictionary secondary, ListMergeMode mode)
        {
            if (main == null || secondary == null || secondary.Count == 0)
            {
                return;
            }

            if ((main.IsFixedSize || main.IsReadOnly) && mode != ListMergeMode.Replace)
            {
                XmlController.Log("[ERROR] Main dictionary is fixed size or read only.");
                return;
            }

            switch (mode)
            {
            case ListMergeMode.Replace:

                // Clear list and add all values from other list.
                main.Clear();
                foreach (var key in secondary.Keys)
                {
                    main.Add(key, secondary[key]);
                }

                break;

            case ListMergeMode.Append:

                // Simply add all values from secondary to primary.
                foreach (var key in secondary.Keys)
                {
                    if (!main.Contains(key))
                    {
                        main.Add(key, secondary[key]);
                    }
                }

                break;

            case ListMergeMode.MergeReplace:     // Merge replace and normal merge have the same behaviour on dictionaries.
            case ListMergeMode.Merge:

                // Add all values from other list, if they are not already in the list.
                foreach (var key in secondary.Keys)
                {
                    if (!main.Contains(key))
                    {
                        main.Add(key, secondary[key]);
                    }
                    else
                    {
                        main[key] = secondary[key];
                    }
                }

                break;

            case ListMergeMode.Subtract:

                // Remove pairs from main if key is in secondary
                foreach (var key in secondary.Keys)
                {
                    if (main.Contains(key))
                    {
                        main.Remove(key);
                    }
                }
                break;

            default:
                XmlController.Log($"[ERROR] {mode} is not implemented.");
                break;
            }
        }