/// <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); } } } }
/// <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); }