/// <summary> /// Check an XmlElement to see if it includes a specific "Type" attribute. If so, return /// the actual Type associated with that attribute's value. /// </summary> /// <param name="_xml">The XmlElement containing the field's information</param> /// <param name="_defaultType"> /// If there is no Type attribute, then return this value as the "default" /// </param> /// <returns> /// NULL if no Type attribute was found, or the Type object corresponding the value of /// the Type attribute /// </returns> /// <exception cref="InvalidOperationException"> /// Thrown if there is a Type attribute, but that attribute's value cannot be turned /// into a Type object. /// </exception> private Type GetTypeFromXmlOrDefault(XmlElement _xml, Type _defaultType) { var sType = XmlExtensions.GetAttributeValue(_xml, m_context.TypeAttributeName); if (sType == null) // There is no explicit Type specifier (XmlAttribute) { return(_defaultType); } var explicitType = Lib.BetterGetType(sType, true); if (explicitType == null) { // The XML had an explicit Type, but that Type couldn't be found. So... If we // are trying to deserialize a Type that uses EntitySemantics, we assume that // the destination Type is sufficient to figure out what to deserialize. If the // destination Type is wholly inadequate to "receive" the data in the XML, the // application is at fault. For Entities, this type of mismatch is OK. if (CEntityTypeData.UsesEntitySemantics(_defaultType)) { return(_defaultType); } // However, if this isn't using EntitySemantics, then throw an exception as the // Type is unknown. throw new XDeserializationError("An Attribute was found for an Explicit Type, but it could not be turned into a Type: " + sType); } return(explicitType); }
/// <summary> /// Using childNodes from an element, deserialize an array by deserializing the /// individual elements into the array elements. /// </summary> /// <param name="_xml"> /// The XML parent node containing the array elements as childNodes /// </param> /// <param name="_arrayHelper">The ArrayDeserializationHelper</param> private void DeserializeArrayFromElements(XmlElement _xml, CArrayDeserializationHelper _arrayHelper) { foreach (XmlNode node in _xml.ChildNodes) { if (!(node is XmlElement elem)) { throw new XDeserializationError(node.NodeType.ToString() + " was the nodetype found (instead of XmlElement) as a child of the array's XML"); } int[] indicies = null; var sIndex = XmlExtensions.GetAttributeValue(elem, m_context.ArrayIndexAttributeName); if (sIndex != null) { indicies = sIndex.Split(',').Select(s => int.Parse(s)).ToArray(); } if (indicies != null && indicies.Length > 0) { _arrayHelper.SetIndicies(indicies); } var obj = FrameworkDeserialize(elem, _arrayHelper.ElementType); _arrayHelper.Add(obj); } }
/// <summary> /// The framework will deserialize the object. No more surrogates or other "exceptional" /// behaviour. /// </summary> /// <param name="_xml"></param> /// <param name="_type"></param> /// <param name="_workingObject"></param> private void HandleDeserialization(XmlElement _xml, Type _type, CWorkingObject _workingObject) { // Strings are really easy- just return the "InnerText" if (_type == TYPEOF_STRING) { _workingObject.Set(Environment.ExpandEnvironmentVariables(_xml.InnerText)); } // Primitives are also pretty easy, only because of the "Convert" class else if (_type.IsPrimitive) { _workingObject.Set(Convert.ChangeType(_xml.InnerText, _type)); } // Check for an Array else if (_type.IsArray || XmlExtensions.HasAttribute(_xml, m_context.ArrayAttributeName)) { DeserializeArray(_xml, _type, _workingObject); } else if (_type.IsEnum) { _workingObject.Set(Enum.Parse(_type, _xml.InnerText, false)); } else // Handle ref-type fields and base classes. { DeserializeReferenceType(_xml, _type, _workingObject); } }
/// <summary> /// Check the easiest forms of deserialization- Null and RefTo an object already /// deserialized. /// </summary> /// <param name="_xml">The XML that's being deserialized</param> /// <param name="_workObj"> /// The Object that was deserialized (when the return value is TRUE) /// </param> /// <returns> /// TRUE means that the _workObj parameter contains valid data and nothing further /// should be done, FALSE means that no deserialization was performed. /// </returns> private bool CheckNullAndReference(XmlElement _xml, CWorkingObject _workObj) { // Check the XML to see if this is a NULL object reference. If it is, then return // TRUE. if (XmlExtensions.HasAttribute(_xml, m_context.NullAttributeName)) { return(true); } // Check the XML to see if its referring to some other object. var referTo = XmlExtensions.GetAttributeValue(_xml, m_context.ReferToAttributeName); if (referTo != null) // There was a reference to another object, so handle it and return that other object. { // Check the table for the RefID to get the object. The reference must be in the // table already because forward-looking references are not supported. if (!m_references.TryGetValue(referTo, out var obj)) { throw new XUnknownReference("All object-references must be to backward-defined objects. The RefID " + referTo + " has not been defined yet."); } _workObj.Set(obj); return(true); } // Check to see if this element is associating the XML with a reference ID var refId = XmlExtensions.GetAttributeValue(_xml, m_context.ReferenceIdAttributeName); if (refId != null) { _workObj.SetRefInfo(this, refId); } return(false); }
/// <summary> /// Given information about the current state, create an ArrayDeserializationHelper to /// help with the deserialization process /// </summary> /// <param name="_xml">The XML containing the Array information</param> /// <param name="_type">The Type of the array expected</param> /// <param name="_workingObject"> /// A "Working Object", likely from a surrogate that didn't complete the deserialization /// </param> /// <param name="_inferredLength"> /// The Inferred Length of the array, from a "Condensed Array" already found in the XML /// </param> /// <returns> /// A properly formed <see cref="CArrayDeserializationHelper"/> object. /// </returns> private CArrayDeserializationHelper CreateArrayDeserializationHelper(XmlElement _xml, Type _type, CWorkingObject _workingObject, int _inferredLength) { CArrayDeserializationHelper arrayHelper; if (_workingObject.IsSet) { arrayHelper = new CArrayDeserializationHelper(_workingObject.WorkingObject as Array); } else { // Make sure that the array type actually is an array and it has an ElementType var arrType = _type; if (!arrType.IsArray) { throw new XDeserializationError( "The Type specified is not an array or it does not have an ElementTypes associated with it- " + arrType.ToString()); } // Get info from the XML var sLengths = XmlExtensions.GetAttributeValue(_xml, m_context.ArrayAttributeName); var lengths = sLengths?.Split(',').Select(s => int.Parse(s)).ToArray(); var sLowerBounds = XmlExtensions.GetAttributeValue(_xml, m_context.ArrayLowerBoundAttribute); var lowerBounds = sLowerBounds?.Split(',').Select(s => int.Parse(s)).ToArray(); if (lengths == null) { if (_inferredLength > -1) { lengths = new int[] { _inferredLength } } ; else { lengths = new int[] { InferArrayLength(_xml) } }; // This assumes a 1-dim array at this point. } arrayHelper = new CArrayDeserializationHelper(arrType.GetElementType(), lengths, lowerBounds); } return(arrayHelper); }
/// <summary> /// This routine will try to figure out how large an array is based on the nodes in the /// XML /// </summary> /// <param name="_xml"></param> /// <returns></returns> private int InferArrayLength(XmlElement _xml) { // XmlNode node = _xml.SelectSingleNode( @"*/*[@idx][last()]" ); var index = 0; foreach (XmlNode node in _xml.ChildNodes) { var sIndex = XmlExtensions.GetAttributeValue(node, m_context.ArrayIndexAttributeName); if (!string.IsNullOrEmpty(sIndex)) { index = int.Parse(sIndex); } index++; } return(index); }
/// <summary> /// Check to see if the object has been serialized before. If it has, then make sure /// that the reference information for it is added. /// </summary> /// <remarks>This routine has the overloaded</remarks> /// <param name="_object">The object that's being serialized</param> /// <param name="_elementForObject"> /// The element that the object is being serialized to /// </param> /// <returns>TRUE if the object is a duplicate, FALSE if it is a "new" object</returns> private bool HandleDuplicateReferences(object _object, XmlElement _elementForObject) { var oType = _object.GetType(); if (oType.IsPrimitive) { return(false); } if (oType == TYPEOF_STRING && !m_context.DuplicateStringsCanBeReferredTo) { return(false); } if (oType.IsEnum) { return(false); } if (m_references.TryGetValue(_object, out var refElem)) // The object was serialized already { if (ReferenceEquals(_elementForObject, refElem)) // If this IS the referenced element, then skip it { return(false); } var refId = XmlExtensions.GetAttributeValue(refElem, m_context.ReferenceIdAttributeName); if (refId == null) // The object that was serialized doesn't have its RefID set yet { refId = m_refId.ToString(); XmlExtensions.AddAttribute(refElem, m_context.ReferenceIdAttributeName, refId); m_refId++; } // Add the "ReferTo" attribute to the xml XmlExtensions.AddAttribute(_elementForObject, m_context.ReferToAttributeName, refId); // Remove a "Type" attribute (if it exists) because this becomes redundant with // the presence of RefTo XmlExtensions.RemoveAttribute(_elementForObject, m_context.TypeAttributeName); return(true); } m_references.Add(_object, _elementForObject); return(false); }
/// <summary> /// This "different" serializer handles Properties, not Fields, and conforms to the /// conventions found in Microsoft's Entity Framework /// </summary> /// <param name="_object"></param> /// <param name="_useType"></param> /// <param name="_elementForObject"></param> private void SerializeUsingEntitySemantics(object _object, Type _useType, XmlElement _elementForObject) { XmlExtensions.AddAttribute(_elementForObject, m_context.UseEntitySemanticsAttributeName, "1"); // Gets (cached) type data pertinent to serializing an Entity var typeData = CEntityTypeData.GetTypeData(_useType); // First serialize all the "single" properties- No collections for (var i = 0; i < typeData.NonCollectionProperties.Length; i++) { var prop = typeData.NonCollectionProperties[i]; var val = prop.GetValue(_object); FrameworkSerialize(prop.Name, val, _elementForObject, prop.PropertyType); } // Now serialize all the collections which are presumed to be the "Many" in a // Many-To-One relationship. The exact Type of the collection in this object is // irrelevant because the collection should merely implement ICollection for the // deserialization, and the deserialization target class should determine the exact // collection Type, or if its an Interface the PreferredCollectionType on the // AUseEntitySemantics attribute will dictate what to create. for (var i = 0; i < typeData.CollectionProperties.Length; i++) { var col = typeData.CollectionProperties[i]; var collectionElement = XmlExtensions.AddElement(_elementForObject, col.Name); Type elementType = null; // set if the PropertyType is a generic collection if (col.PropertyType.IsGenericType) { elementType = col.PropertyType.GetGenericArguments()[0]; } foreach (var item in col.GetValue(_object) as System.Collections.IEnumerable) { FrameworkSerialize(m_context.ArrayElementName, item, collectionElement, elementType); } } }
/// <summary> /// Create an XmlElement for an object. Do not populate the new element. /// </summary> /// <remarks> /// FieldRenamers are NOT called at this level- the caller of this method needs to /// handle all field renaming /// </remarks> /// <param name="_elementName"> /// The name that should be assigned to the newly created element /// </param> /// <param name="_expectedType"> /// The expected type of the object. Used to decide whether or not to add explicit Type /// data /// </param> /// <param name="_object">The object that is being serialized</param> /// <param name="_parentNode">The XML node that the new node will be a child of</param> /// <returns>An XmlElement with some basic attributes for an object</returns> private XmlElement CreateElementForValue(string _elementName, object _object, XmlNode _parentNode, Type _expectedType) { var fixedElementName = FixMemberName(_elementName); var elem = XmlExtensions.CreateElement(_parentNode, fixedElementName); if (_object == null) { XmlExtensions.AddAttribute(elem, m_context.NullAttributeName, m_context.NullAttributeValue); } else { var oType = _object.GetType(); oType = CEntityTypeData.StripProxyType(oType); if (_expectedType != oType) // There must be a Type attribute added { XmlExtensions.AddAttribute(elem, m_context.TypeAttributeName, oType.AssemblyQualifiedName); } } return(elem); }
/// <summary> /// Use this method when an Array field needs to be added to the XML serialization /// </summary> /// <remarks> /// For element types that are primitives and strings, build a comma-separated list of /// values that should end up taking less space. For all other element types, create /// child-elements for each array element. /// </remarks> /// <param name="_array">The array to add to the XML Element</param> /// <param name="_elementToAddTo">The XML Element that is to contain the array</param> private void AddArrayToXml(Array _array, XmlElement _elementToAddTo) { if (_array.Rank != 1) { AddMultiDimensionalArray(_array, _elementToAddTo); return; } var arrayType = _array.GetType(); var elementType = arrayType.GetElementType(); var count = _array.Length; var lowerBound = _array.GetLowerBound(0); var upperBound = _array.GetUpperBound(0); XmlExtensions.AddAttribute(_elementToAddTo, m_context.ArrayAttributeName, count); if (lowerBound != 0) { XmlExtensions.AddAttribute(_elementToAddTo, m_context.ArrayLowerBoundAttribute, lowerBound); } if (elementType.IsPrimitive && !m_context.AllArraysHaveExplicitElements) { // Helper doesn't care what the element type is, so we screen it first by making // sure its a primitive. _elementToAddTo.InnerText = Lib.ConvertArrayToString(_array); } else if (elementType == TYPEOF_STRING && !m_context.AllArraysHaveExplicitElements) { // Strings could theoretically be treated with the same helper used above IF // they never contained commas. var str = ConvertStringArrayToCommaList(_array); _elementToAddTo.InnerText = str; } else { var skipped = false; var elementName = GetNameForCollectionElement(); for (var i = lowerBound; i <= upperBound; i++) { var arrayElementValue = _array.GetValue(i); if (arrayElementValue == null && m_context.RemoveNullValuesFromXml) { skipped = true; } else { var elem = FrameworkSerialize(elementName, arrayElementValue, _elementToAddTo, elementType); if (m_context.ArrayElementsIncludeIndicies || skipped) { XmlExtensions.AddAttribute(elem, m_context.ArrayIndexAttributeName, i); } skipped = false; } } } }