/// <summary> /// Sets the value of field referred to by specified mapping, inside specified /// instance with specified type /// </summary> /// <param name="desc">FieldMapping object representing the field</param> /// <param name="fieldValue">Value to set</param> /// <param name="contType">Type of containing object</param> /// <param name="contInst">Containing object</param> /// <returns></returns> public static bool SetFieldValue(FieldMapping desc, Object fieldValue, Type contType, Object contInst) { bool written = false; if (null == desc.ValueSetter) // We haven't figured this out before? { PropertyInfo pi = null; MethodInfo mi = null; // // If the field is public, then that's what we'll use to set the value! // if (desc.FieldInfo.IsPublic) { desc.ValueSetter = desc.FieldInfo; desc.ValueSetType = desc.FieldInfo.FieldType; } else { String pascalName = Helpers.ToPascalCase(desc.ObjBindingName); // // Now we look for a write-access property with a pascal case version // of the field name (e.g. Documentation). //! What about overloads? // if (null != (pi = contType.GetProperty(pascalName)) && pi.CanWrite) { desc.ValueSetter = pi; desc.ValueSetType = pi.PropertyType; } else if (null != (mi = contType.GetMethod("set" + pascalName, new Type[] { desc.FieldInfo.FieldType }))) { desc.ValueSetter = mi; desc.ValueSetType = desc.FieldInfo.FieldType; } else { Console.WriteLine(String.Format("Cannot find {0} write property in {1}", pascalName, contInst.GetType())); } } } if (null == desc.ValueSetter) { throw new Exception(String.Format("Unable to find a way to populate value of {0} in {1}", desc.XmlName, null != contInst ? contInst.ToString() : "null")); } written = InvokeSet(desc.ValueSetter, fieldValue, desc.ValueSetType, contInst); return(written); }
/// <summary> /// Processes a dictionary object, by writing all its key based elements /// </summary> /// <typeparam name="TA">Type of key within the dictionary</typeparam> /// <typeparam name="TB">Type of value within the dictionary</typeparam> /// <param name="dict">Dictionary object</param> /// <param name="dictMapping">Field mapping describing the dictionary itself within its containing object</param> /// <param name="writer">XmlWriter object</param> private void ProcessDictElements <TA, TB>(IDictionary <TA, TB> dict, FieldMapping dictMapping, XmlWriter writer) { foreach (TA key in dict.Keys) { TB entry = dict[key]; writer.WriteStartElement(dictMapping.XmlName); // Entry tag String mapKeyValue = key.ToString(); writer.WriteAttributeString(dictMapping.KeyName, mapKeyValue); // Write key attribute Write(entry, null, writer, dictMapping.MapEntryXmlName); // Write the entry writer.WriteEndElement(); // Close entry tag } }
/// <summary> /// Writes out those fields of specified object that are mapped to XML attributes /// </summary> /// <param name="obj">Object to write its attributes</param> /// <param name="em">Element mapping of the object</param> /// <param name="writer">XmlWriter object</param> private void WriteAttributes(Object obj, ElementMapping em, XmlWriter writer) { Type objType = obj.GetType(); for (int i = 0, length = null != em.Attributes ? em.Attributes.Count : 0; i < length; i++) { FieldMapping fm = em.Attributes[i]; Object value = Helpers.GetFieldValue(fm, objType, obj, typeof(String)); if (null != value) { String strValue = value.ToString(); writer.WriteAttributeString(fm.XmlName, strValue); } } }
/// <summary> /// Returns an element mapping object representing the specified type /// </summary> /// <param name="t">Type to return its element mapping</param> /// <returns>Element mapping object if found, otherwise null</returns> public ElementMapping GetElementMapping(Type t) { ElementMapping res = null; String cacheKey = t.FullName; if (!elemCache.TryGetValue(cacheKey, out res)) { ElementMapping em = new ElementMapping(); em.XmlName = Helpers.ExtractElementName(t); FieldInfo[] fis = t.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); for (int i = 0; i < fis.Length; i++) { FieldInfo fi = fis[i]; FieldMapping desc = GetFieldMapping(fi, fi.Name); if (null == desc) { continue; } switch (desc.NodeType) { case NodeType.Attribute: em.AddAttribute(desc); break; default: em.AddSubElement(desc); break; } } elemCache.Add(cacheKey, em); res = em; } return(res); }
/// <summary> /// Populates those field of the specified object that are mapped to XML attributes /// </summary> /// <param name="t">Type of object</param> /// <param name="inst">Object to populate</param> /// <param name="reader">XmlReader to read from</param> private void PopulateAttributes(Type t, Object inst, XmlReader reader) { int i = -1; for (i = 0; i < reader.AttributeCount; i++) { reader.MoveToAttribute(i); FieldMapping desc = ots.LookupField(t, reader.Name); if (null != desc) { Helpers.SetFieldValue(desc, reader.Value, t, inst); } } if (i >= 0) { reader.MoveToElement(); } }
/// <summary> /// Looks up and returns a field mapping object given the containing type and the /// XML tag/attribute name within the scope of the containing object /// </summary> /// <param name="t">The type of the containing object</param> /// <param name="xmlName">The XML tag/attribute name within the scope of the /// containing object</param> /// <returns>FieldMapping object if found, otherwise null</returns> public FieldMapping LookupField(Type t, String xmlName) { FieldMapping res = null; String cacheKey = t.FullName + "_" + xmlName; if (fieldMappingCache.TryGetValue(cacheKey, out res)) { return(res); } if (null == res) { FieldInfo[] fis = null; if (!fieldsCache.TryGetValue(t.FullName, out fis)) { fis = t.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); fieldsCache.Add(t.FullName, fis); } for (int i = 0; i < fis.Length; i++) { FieldInfo fi = fis[i]; FieldMapping desc = GetFieldMapping(fi, xmlName); if (null == desc) { continue; } if (String.Equals(xmlName, desc.XmlName)) { res = desc; break; } } } return(res); }
/// <summary> /// Writes out the specified object. This method writes the element for the object, and /// recursively writes out all attributes and sub elements. /// </summary> /// <param name="obj">Object to write</param> /// <param name="fm">Field mapping of the object within its containing object (optional). /// When provided, the logic will use it to determine whether element is to be "inline"'d, /// in which case the logic will not write an outer tag for it. Also the XmlName field of the /// mapping will take precedence in determining the tag name over the name mentioned in /// the element mapping. /// </param> /// <param name="writer">XmlWriter object</param> /// <param name="tagName">Specifies the tag name to use for the object (optional). This /// parameter can be used to override what the tag name that the logic would normally use, /// it is mainly to be used when writing collections, since the name of each entry's tag /// can be customized at the collection declaration level (e.g. via [ElementMap] or /// [ElemenList] attributes). When this parameter is set to null, the logic will get the /// name from either the field mapping object (if provided) or from the element mapping object. /// </param> private void Write(Object obj, FieldMapping fm, XmlWriter writer, String tagName) { Type objType = obj.GetType(); ElementMapping em = ots.GetElementMapping(objType); if (null == em) { throw new ArgumentException("Don;t know how to handle this object", objType.ToString()); } String xmlName = tagName; // Allow caller to override tag name // // If the tag name was not overriden by the caller, then it would be the name // specified by the mapping in the elment's container, if not specified then // it is the name as specified by the element mapping. // if (null == xmlName) // Tag name not provided, use the one in field mapping { xmlName = null != fm ? fm.XmlName : em.XmlName; } // // For nodes that are marked as inline, we don't write a tag for them. // bool inline = null != fm ? fm.Inline : false; if (!inline) { writer.WriteStartElement(xmlName); // Start of element WriteAttributes(obj, em, writer); // Write attributes } int i = 0, length = length = null != em.SubElements ? em.SubElements.Count : 0; // // If the element has no sub-elements, we treat it as a leaf node so we write its // body value. Note that we do the XML encoding ourselves because XmlWriter // implementation does not encode single or double quotes within the body of a tag, // but SimpleXml framework does. // if (0 == length) { writer.WriteRaw(Helpers.XmlEncode(obj.ToString())); } else // Otherwise, drill down the object { // // Write sub elements. // for (i = 0; i < length; i++) { FieldMapping subMapping = em.SubElements[i]; Object value = null; switch (subMapping.NodeType) { case NodeType.Element: value = Helpers.GetFieldValue(subMapping, objType, obj, subMapping.ValueType); if (null != value) { Write(value, subMapping, writer, null); // Drill down } break; case NodeType.Map: { // // Note that we assume that map based fields are using an IDictionary // based object, with its key being of type String. // value = Helpers.GetFieldValue(subMapping, objType, obj, subMapping.ValueType); if (null != value) { Type gt = typeof(IDictionary <,>); if (null != value.GetType().GetInterface("IDictionary`2")) { MethodInfo mi = this.GetType().GetMethod("ProcessDictElements", BindingFlags.NonPublic | BindingFlags.Instance); MethodInfo si = mi.MakeGenericMethod(value.GetType().GetGenericArguments()); si.Invoke(this, new Object[] { value, subMapping, writer }); } } break; } case NodeType.List: { // // Note that we assume that list based fields are using an IList based object. // value = Helpers.GetFieldValue(subMapping, objType, obj, subMapping.ValueType); if (null != value) { Type gt = typeof(IList <>); if (null != value.GetType().GetInterface("IList`1")) { MethodInfo mi = this.GetType().GetMethod("ProcessListElements", BindingFlags.NonPublic | BindingFlags.Instance); MethodInfo si = mi.MakeGenericMethod(value.GetType().GetGenericArguments()); si.Invoke(this, new Object[] { value, subMapping, writer }); } } break; } } } } if (!inline) { writer.WriteEndElement(); // End of element } }
/// <summary> /// Returns the value of field referred to be specified mapping, inside specified /// instance with specified type /// </summary> /// <param name="desc">FieldMapping object representing the field</param> /// <param name="contType">Type of containing object</param> /// <param name="contInst">Contianing object</param> /// <param name="valueAsType"></param> /// <returns></returns> public static Object GetFieldValue(FieldMapping desc, Type contType, Object contInst, Type valueAsType) { Object res = null; if (null == desc.ValueGetter) // We haven't figured this out before? { PropertyInfo pi = null; MethodInfo mi = null; // // If the field is public, then that's what we'll use to get the value! // if (desc.FieldInfo.IsPublic) { desc.ValueGetter = desc.FieldInfo; desc.ValueGetType = desc.FieldInfo.FieldType; } else { String pascalName = Helpers.ToPascalCase(desc.ObjBindingName); // // Now we look for a read-access property with a pascal case version // of the field name (e.g. Documentation). //! What about overloads? // if (null != (pi = contType.GetProperty(pascalName)) && pi.CanRead) { desc.ValueGetter = pi; desc.ValueGetType = pi.PropertyType; } else if (null != (mi = contType.GetMethod("get" + pascalName))) { desc.ValueGetter = mi; desc.ValueGetType = desc.FieldInfo.FieldType; } else { Console.WriteLine(String.Format("Cannot find {0} read property in {1}", pascalName, contInst.GetType())); } } } if (null == desc.ValueGetter) { throw new Exception(String.Format("Unable to find a way to read value of {0} in {1}", desc.ObjBindingName, null != contInst ? contInst.ToString() : "null")); } res = InvokeGet(desc.ValueGetter, desc.ValueType, valueAsType, contInst); if (null != res && !IsTypeDescendantOf(res, valueAsType)) { if (res is Boolean) { res = ((Boolean)res) ? "true" : "false"; } else { res = ChangeType(res, valueAsType); } } return(res); }
/// <summary> /// Reads and returns an object of specified type from specified XmlReader. This /// method instantiates an object of specified type, and recursively populates /// its fields from attributes and sub elements. /// </summary> /// <param name="t">Type of object</param> /// <param name="r">XmlReader object</param> /// <returns>The read object</returns> public Object Read(Type t, XmlReader r) { using (XmlReader reader = r.ReadSubtree()) { reader.Read(); // // We assume reader is positioned on the XML element for the specified type // Object res = InstantiateObject(t); PopulateAttributes(t, res, reader); // Populate from attributes if any // // This variable is used in the situations when we read the content // of a node, in which case the pointer has already been advanced to // the next node so there's no need to call Read(). // bool dontAdvance = false; while (dontAdvance || reader.Read()) { dontAdvance = false; // Clear the flag if (reader.NodeType == XmlNodeType.EndElement || reader.NodeType == XmlNodeType.Text) { continue; } FieldMapping desc = ots.LookupField(t, reader.Name); if (null == desc) { //Console.WriteLine(string.Format("{0} skipped. within type: {1}", reader.Name, t.ToString())); reader.Skip(); continue; // Skip ones we don't care for } Object fieldValue = null; switch (desc.NodeType) { case NodeType.Element: if (Helpers.IsSerializable(desc.FieldInfo.FieldType)) { fieldValue = Read(desc.FieldInfo.FieldType, reader); } else { fieldValue = reader.ReadElementContentAsString(); dontAdvance = true; } Helpers.SetFieldValue(desc, fieldValue, t, res); break; case NodeType.Map: // // Ensure we have a dictionary instance. // Object dict = desc.FieldInfo.GetValue(res); if (null == dict) { Type t1 = typeof(Dictionary <,>); Type[] args = { desc.MapEntryKeyType, desc.MapEntryValueType }; Type t2 = t1.MakeGenericType(args); dict = Activator.CreateInstance(t2); Helpers.SetFieldValue(desc.ObjBindingName, dict, t, res); } String key = reader.GetAttribute(desc.KeyName); // Get the key // // Get the entry value // fieldValue = null; if (reader.ReadToDescendant(desc.MapEntryXmlName)) { if (Helpers.IsSerializable(desc.MapEntryValueType)) { fieldValue = Read(desc.MapEntryValueType, reader); } else { fieldValue = reader.ReadElementContentAsString(); dontAdvance = true; } if (null != fieldValue) { MethodInfo mi = desc.ValueType.GetMethod("Add"); mi.Invoke(dict, new Object[] { key, fieldValue }); } } break; case NodeType.List: // // Ensure we have a dictionary instance. // Object list = desc.FieldInfo.GetValue(res); if (null == list) { Type t1 = typeof(List <>); Type[] args = { desc.MapEntryValueType }; Type t2 = t1.MakeGenericType(args); list = Activator.CreateInstance(t2); Helpers.SetFieldValue(desc.ObjBindingName, list, t, res); } // // Get the entry value // fieldValue = null; if (Helpers.IsSerializable(desc.MapEntryValueType)) { fieldValue = Read(desc.MapEntryValueType, reader); } else { fieldValue = reader.ReadElementContentAsString(); dontAdvance = true; } if (null != fieldValue) { MethodInfo mi = list.GetType().GetMethod("Add"); mi.Invoke(list, new Object[] { fieldValue }); } break; } } return(res); } }
/// <summary> /// Returns a field mapping object given a FieldInfo object /// </summary> /// <param name="fi">FieldInfo object representing a field within its container</param> /// <param name="cacheKeyValue">Key to be used by field mapping caching</param> /// <returns>FieldMapping object if found, otherwise null</returns> public FieldMapping GetFieldMapping(FieldInfo fi, String cacheKeyValue) { FieldMapping desc = null; BaseAttribute attr = Helpers.ExtractAttribute(fi); if (null != attr) { String cacheKey = fi.DeclaringType.FullName + "_" + cacheKeyValue; if (!fieldMappingCache.TryGetValue(cacheKey, out desc)) { if (attr is XmlAttributeAttribute) { XmlAttributeAttribute attrAttr = attr as XmlAttributeAttribute; desc = new FieldMapping(NodeType.Attribute); desc.XmlName = null != attrAttr.Name ? attrAttr.Name : fi.Name; desc.Required = attrAttr.Required; desc.ValueType = fi.FieldType; desc.ObjBindingName = fi.Name; desc.FieldInfo = fi; } else if (attr is ElementAttribute) { ElementAttribute elemAttr = attr as ElementAttribute; desc = new FieldMapping(NodeType.Element); desc.XmlName = null != elemAttr.Name ? elemAttr.Name : fi.Name; desc.Required = elemAttr.Required; desc.ValueType = fi.FieldType; desc.ObjBindingName = fi.Name; desc.FieldInfo = fi; } else if (attr is ElementMapAttribute) { ElementMapAttribute mapAttr = attr as ElementMapAttribute; desc = new FieldMapping(NodeType.Map); desc.Inline = mapAttr.Inline; desc.KeyName = mapAttr.Key; desc.ValueType = fi.FieldType; desc.ObjBindingName = fi.Name; desc.FieldInfo = fi; // // Get entry element name. // if (fi.FieldType.IsGenericType) { Type[] gargs = fi.FieldType.GetGenericArguments(); desc.MapEntryKeyType = gargs[0]; desc.MapEntryValueType = gargs[1]; desc.MapEntryXmlName = Helpers.ExtractElementName(desc.MapEntryValueType); } // // Get map element name // if (mapAttr.Inline) { desc.XmlName = null != mapAttr.Entry ? mapAttr.Entry : "entry"; } else { desc.XmlName = mapAttr.Name; } } else if (attr is ElementListAttribute) { ElementListAttribute listAttr = attr as ElementListAttribute; desc = new FieldMapping(NodeType.List); desc.Inline = listAttr.Inline; desc.ValueType = fi.FieldType; desc.ObjBindingName = fi.Name; desc.FieldInfo = fi; // // Get entry element name. // if (fi.FieldType.IsGenericType) { Type[] gargs = fi.FieldType.GetGenericArguments(); desc.MapEntryValueType = gargs[0]; //!desc.MapEntryName = Helpers.ExtractElementName(desc.MapEntryValueType); } // // Get list entry name. // if (listAttr.Inline) { String entryName = null != desc.MapEntryValueType ? Helpers.ToCamelCase(desc.MapEntryValueType.Name) : null; desc.XmlName = null != listAttr.Entry ? listAttr.Entry : (null != entryName ? entryName : "entry"); } else { desc.XmlName = listAttr.Name; } } if (desc.XmlName.Equals(cacheKeyValue)) { fieldMappingCache.Add(cacheKey, desc); } } } return(desc); }