/// <summary>
        /// Loads mapping configurations from an XML reader.
        /// </summary>
        public void Load(XmlReader mappingXml)
        {
            while (mappingXml.Read())
            {
                // Check for allowed XML nodex
                if (!(mappingXml.NodeType == XmlNodeType.Element))
                {
                    if (mappingXml.NodeType == XmlNodeType.Text)
                    {
                        throw new MappingConfigurationException(String.Format("Node type {0} is not allowed here.", mappingXml.NodeType), "Object", mappingXml);
                    }
                    else
                    {
                        continue;
                    }
                }
                else if (mappingXml.Name == "MappingConfiguration" || mappingXml.Name == "Mappings")
                {
                    // Read into content
                    continue;
                }
                if (mappingXml.Name == "Object")
                {
                    var objectMapping = new MappingContainer()
                    {
                        Root = this
                    };
                    string typeName = mappingXml.GetAttribute("Type");
                    if (typeName == null)
                    {
                        throw new MappingConfigurationException("'Type' attribute is missing.", "Object", mappingXml);
                    }
                    objectMapping.TargetType = this.ResolveType(typeName);
                    if (objectMapping.TargetType == null)
                    {
                        throw new MappingConfigurationException(String.Format("Type '{0}' could not be found. Did you forget a <Using>? <Using> elements must be defined before any <Object> elements that use it.", typeName), "Object", mappingXml);
                    }

                    if (!mappingXml.IsEmptyElement)
                    {
                        DeserializeMappings(this, objectMapping, mappingXml);
                    }

                    this.Objects.Add(objectMapping.TargetType, objectMapping);
                }
                else if (mappingXml.Name == "Using")
                {
                    this.Usings.Add(mappingXml.GetAttribute("Namespace"));
                }
                else
                {
                    throw new MappingConfigurationException(String.Format("<{0}> is not allowed here.", mappingXml.Name), mappingXml);
                }
            }
        }
        /// <summary>
        /// Parses parentXml and finds MapCommand and ReadCommand objects that should be added to the parent container.
        /// </summary>
        private static void DeserializeMappings(MappingConfiguration config, MappingContainer parent, XmlReader parentXml)
        {
            int    currentDepth = parentXml.Depth;
            string currentName  = parentXml.Name;

            while (parentXml.Read())
            {
                // This is the exit condition
                if (parentXml.NodeType == XmlNodeType.EndElement && parentXml.Name == currentName && parentXml.Depth == currentDepth)
                {
                    break;
                }
                else if (parentXml.NodeType == XmlNodeType.Text)
                {
                    throw new MappingConfigurationException(String.Format("<{0}> is not allowed here.", parentXml.Name), parentXml);
                }
                else if (parentXml.NodeType == XmlNodeType.Element)
                {
                    // Just an alias to make a distinction
                    XmlReader element = parentXml;

                    // If a field is specified, create a read command (implicit or explicit)
                    ReadCommand read  = null;
                    string      field = element.GetAttribute("Field");

                    // Check if mapping command is required
                    bool   required  = true;
                    string srequired = element.GetAttribute("Required");
                    if (srequired != null && !bool.TryParse(srequired, out required))
                    {
                        throw new MappingConfigurationException("Invalid value for Required.", element.Name, element);
                    }

                    if (field != null)
                    {
                        read = new ReadCommand()
                        {
                            Field        = field,
                            VarName      = element.GetAttribute("Var") ?? field,
                            RegexPattern = element.GetAttribute("Regex"),
                            IsRequired   = required
                        };
                    }

                    if (element.Name == "Read")
                    {
                        // Field is required for an explicit read command
                        if (read == null)
                        {
                            throw new MappingConfigurationException("Missing 'Field' attribute.", "Read", element);
                        }

                        // Register it as a read command
                        parent.ReadCommands.Add(read);

                        // Add the command to the parent's inherited list, so that child map commands inherit it also
                        parent.InheritedReads[read.VarName] = read;
                    }

                    else if (element.Name == "Map")
                    {
                        // Handle mappings
                        string to = element.GetAttribute("To");
                        if (to == null)
                        {
                            throw new MappingConfigurationException("Missing 'To' attribute.", "Map", element);
                        }

                        string itemType = element.GetAttribute("ItemType");

                        if (read != null)
                        {
                            read.IsImplicit = true;
                        }

                        MapCommand map = MapCommand.CreateChild(parent, to, element, read, returnInnermost: true, itemType: itemType);

                        // Force parent to re-inherit, and then inherit from it
                        map.Inherit();

                        map.IsRequired = required;

                        // Condition
                        string condition = element.GetAttribute("Condition");
                        if (condition != null)
                        {
                            map.Condition = new EvalComponent(map, condition, element);
                        }

                        // Handle value expressions
                        string valueFormat = element.GetAttribute("Value");
                        try
                        {
                            if (valueFormat != null)
                            {
                                map.Value = new ValueFormat(map, valueFormat, element);
                            }
                            else
                            {
                                if (read != null)
                                {
                                    map.Value = new ValueFormat(map, "{" + read.VarName + "}", element);
                                }
                            }
                        }
                        catch (MappingConfigurationException ex)
                        {
                            throw new MappingConfigurationException(ex.Message, "Map", element, ex);
                        }

                        // Recursively add child nodes
                        if (!element.IsEmptyElement)
                        {
                            DeserializeMappings(config, map, element);
                        }
                    }
                    else
                    {
                        throw new MappingConfigurationException(String.Format("Element '{0}' is not allowed here.", parentXml.Name), parentXml);
                    }
                }
            }
        }
Example #3
0
        /// <summary>
        /// Creates a new map command for a target type, parsing the supplied expression.
        /// </summary>
        /// <param name="returnInnermost">If true, returns the last nested map created if the expression has multiple parts. If false, returns the top level map.</param>
        internal static MapCommand CreateChild(MappingContainer container, string targetExpression, XmlReader xml, ReadCommand implicitRead, bool returnInnermost = false, string itemType = null)
        {
            if (container == null)
            {
                throw new ArgumentNullException("container");
            }

            var map = new MapCommand()
            {
                Parent     = container,
                TargetType = container is MapCommand ? ((MapCommand)container).ValueType : container.TargetType,
                Root       = container.Root
            };

            // Add to the parent
            container.MapCommands.Add(map);

            // Keep track of the first one created
            var outermost = map;

            // No target expression - this is okay, so just return it
            if (String.IsNullOrEmpty(targetExpression))
            {
                return(map);
            }

            // Parse the expression
            MatchCollection matches = _levelRegex.Matches(targetExpression);

            for (int i = 0; i < matches.Count; i++)
            {
                Match match = matches[i];

                // ...................................
                // READ COMMANDS

                if (implicitRead != null)
                {
                    map.ReadCommands.Add(implicitRead);
                }
                map.Inherit();

                // ...................................
                // MEMBER

                Group memberGroup = match.Groups["member"];
                if (!memberGroup.Success)
                {
                    throw new MappingConfigurationException(String.Format("'{0}' is not a valid target for a map command.", targetExpression), "Map", xml);
                }

                // Do some error checking on target member name type
                string       targetMemberName = memberGroup.Value;
                MemberInfo[] possibleMembers  = map.TargetType.GetMember(targetMemberName, BindingFlags.Instance | BindingFlags.Public);
                if (possibleMembers == null || possibleMembers.Length < 1)
                {
                    throw new MappingConfigurationException(String.Format("The member '{0}' could not be found on {1}. Make sure it is public and non-static.", targetMemberName, map.TargetType), "Map", xml);
                }
                if (possibleMembers.Length > 1)
                {
                    throw new MappingConfigurationException(String.Format("'{0}' matched more than one member in type {1}. Make sure it is a property or field.", targetMemberName, map.TargetType));
                }
                MemberInfo member = possibleMembers[0];
                if (member.MemberType != MemberTypes.Field && member.MemberType != MemberTypes.Property)
                {
                    throw new MappingConfigurationException(String.Format("'{0}' is not a property or field and cannot be mapped.", targetMemberName), "Map", xml);
                }
                if (member.MemberType == MemberTypes.Property && !((PropertyInfo)member).CanWrite)
                {
                    throw new MappingConfigurationException(String.Format("'{0}' is a read-only property and cannot be mapped.", targetMemberName), "Map", xml);
                }

                map.TargetMember = member;

                // ...................................
                // INDEXER

                // TODO: support more than one indexer (obj[9,2]) + support sequence of indexers (obj[9][2])

                Group indexerGroup = match.Groups["indexer"];
                if (indexerGroup.Success)
                {
                    string indexer    = indexerGroup.Value.Trim();
                    Type   memberType = null;

                    // Determine indexer type
                    if (member.MemberType == MemberTypes.Property)
                    {
                        memberType = ((PropertyInfo)member).PropertyType;
                    }
                    else
                    {
                        memberType = ((FieldInfo)member).FieldType;
                    }

                    PropertyInfo    itemProp = memberType.GetProperty("Item");
                    ParameterInfo[] indexers = null;
                    if (itemProp != null)
                    {
                        indexers = itemProp.GetIndexParameters();
                    }
                    if (indexers == null || indexers.Length == 0)
                    {
                        throw new MappingConfigurationException(String.Format("'{0}' does not support indexers.", targetMemberName), "Map", xml);
                    }
                    if (indexers.Length > 1)
                    {
                        throw new MappingConfigurationException(String.Format("'{0}' has an index with more than one parameter - not currently supported.", targetMemberName), "Map", xml);
                    }
                    map.IndexerType = indexers[0].ParameterType;
                    map.ValueType   = itemType == null ? itemProp.PropertyType : Type.GetType(itemType);

                    if (indexer.StartsWith("{") && indexer.EndsWith("}"))
                    {
                        // This is an eval indexer
                        map.Indexer = new EvalComponent(map, indexer.Substring(1, indexer.Length - 2), xml, inheritedReadOnly: true);
                    }
                    else if (indexer.Length > 0)
                    {
                        // No eval required, convert the key from string
                        TypeConverter converter = TypeDescriptor.GetConverter(map.IndexerType);
                        if (converter == null || !converter.IsValid(indexer))
                        {
                            throw new MappingConfigurationException(String.Format("'{0}' cannot be converted to {1} for the {2} indexer.", indexer, map.IndexerType, targetMemberName), "Map", xml);
                        }

                        map.Indexer = converter.ConvertFromString(indexer);
                    }
                    else
                    {
                        // Empty indexers (i.e. 'Add' method) only valid with IList
                        if (!typeof(IList).IsAssignableFrom(memberType))
                        {
                            throw new MappingConfigurationException(String.Format("Invalid indexer defined for target '{0}'.", targetMemberName), "Map", xml);
                        }

                        map.Indexer = MapCommand.ListAddingMode;
                    }
                }

                // ...................................
                // VALUE TYPE

                Group valueTypeGroup = match.Groups["valueType"];
                if (valueTypeGroup.Success)
                {
                    string valueTypeName = valueTypeGroup.Value;

                    map.ValueType = map.Root.ResolveType(valueTypeName);

                    if (map.ValueType == null)
                    {
                        throw new MappingConfigurationException(String.Format("The type '{0}' cannot be found - are you missing a '<Using>'?", valueTypeName), "Map", xml);
                    }
                }
                else
                {
                    // Use the target member's value type only if no value type was found before (in the case of an indexer, for example)
                    if (map.ValueType == null)
                    {
                        map.ValueType = member.MemberType == MemberTypes.Property ?
                                        ((PropertyInfo)member).PropertyType :
                                        ((FieldInfo)member).FieldType;
                    }
                }

                // ...................................
                // DRILL DOWN

                if (i < matches.Count - 1)
                {
                    // Since there are more expression matches, create a child map and continue the loop
                    var child = new MapCommand()
                    {
                        TargetType = map.ValueType,
                        Parent     = map,
                        Root       = map.Root,
                        IsImplicit = true
                    };

                    map.MapCommands.Add(child);
                    map = child;
                }
            }

            // ...................................

            return(returnInnermost ? map : outermost);
        }