#region Using Statements
using ANX.Framework.Content.Pipeline.Helpers;
using ANX.Framework.NonXNA.Development;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml;
#endregion
// This file is part of the ANX.Framework created by the
// "ANX.Framework developer group" and released under the Ms-PL license.
// For details see: http://anxframework.codeplex.com/license
namespace ANX.Framework.Content.Pipeline.Serialization.Intermediate
{
///
/// Provides an implementation of many of the methods of IntermediateSerializer.
/// Deserializes and tracks state for shared resources and external references.
///
[Developer("KorsarNek")]
[TestState(TestStateAttribute.TestState.InProgress)]
[PercentageComplete(100)]
public sealed class IntermediateReader
{
private struct ExternalReferenceFixup
{
public string ID;
public Type TargetType;
public Action Fixup;
}
private string basePath;
private Dictionary>> sharedResourceFixups = new Dictionary>>();
private List externalReferenceFixups = new List();
private XmlTypeNameReader xmlTypeNameReader;
///
/// Gets the parent serializer.
///
public IntermediateSerializer Serializer
{
get;
private set;
}
///
/// Gets the XML input stream.
///
public IntermediateXmlReader Xml
{
get;
private set;
}
internal string BasePath
{
get { return this.basePath; }
}
internal IntermediateReader(IntermediateSerializer serializer, IntermediateXmlReader xmlReader, string basePath, XmlTypeNameContainer container)
{
this.Serializer = serializer;
this.Xml = xmlReader;
this.xmlTypeNameReader = new XmlTypeNameReader(container, xmlReader);
if (basePath != null)
{
basePath = Path.GetFullPath(basePath);
}
this.basePath = basePath;
}
///
/// Reads a single object from the input XML stream.
///
///
/// The format expected by the type serializer.
///
///
///
/// Will be triggered if a multidimensional array is passed.
/// Will be thrown if no parameterless constructor for the corresponding can be found.
///
public T ReadObject(ContentSerializerAttribute format)
{
return this.ReadObjectInternal(format, this.Serializer.GetTypeSerializer(typeof(T)), null);
}
///
/// Reads a single object from the input XML stream, optionally specifying an existing instance to receive the data.
///
///
/// The format of the XML.
/// The object receiving the data, or null if a new instance should be created.
///
///
///
/// Will be triggered if a multidimensional array is passed.
/// Will be thrown if no parameterless constructor for the corresponding can be found.
///
public T ReadObject(ContentSerializerAttribute format, T existingInstance)
{
return this.ReadObjectInternal(format, this.Serializer.GetTypeSerializer(typeof(T)), existingInstance);
}
///
/// Reads a single object from the input XML stream, using the specified type hint.
///
///
/// The format expected by the type serializer.
/// The type serializer.
///
///
///
///
public T ReadObject(ContentSerializerAttribute format, ContentTypeSerializer typeSerializer)
{
return this.ReadObjectInternal(format, typeSerializer, null);
}
///
/// Reads a single object from the input XML stream using the specified type hint, optionally specifying an existing instance to receive the data.
///
///
/// The format of the XML.
/// The type serializer.
/// The object receiving the data, or null if a new instance should be created.
///
///
///
///
public T ReadObject(ContentSerializerAttribute format, ContentTypeSerializer typeSerializer, T existingInstance)
{
return this.ReadObjectInternal(format, typeSerializer, existingInstance);
}
private T ReadObjectInternal(ContentSerializerAttribute format, ContentTypeSerializer typeSerializer, object existingInstance)
{
if (typeSerializer == null)
throw new ArgumentNullException("typeSerializer");
if (format == null)
{
throw new ArgumentNullException("format");
}
if (!format.FlattenContent)
{
if (!this.Xml.CheckForElement(format.ElementName))
throw ExceptionHelper.CreateInvalidContentException(this.Xml, this.BasePath, null, string.Format("XML element \"{0}\" not found.", format.ElementName));
string attribute = this.Xml.GetAttribute("Null");
if (attribute != null && XmlConvert.ToBoolean(attribute))
{
if (!format.AllowNull)
throw ExceptionHelper.CreateInvalidContentException(this.Xml, this.BasePath, null, string.Format("XML element \"{0}\" is not allowed to be null.", format.ElementName));
this.Xml.Skip();
return default(T);
}
else if (this.Xml.MoveToAttribute("Type"))
{
Type type = this.ReadTypeName();
if (!typeSerializer.TargetType.IsAssignableFrom(type))
throw ExceptionHelper.CreateInvalidContentException(this.Xml, this.BasePath, null, string.Format("XML \"Type\" attribute is invalid. Expecting a subclass of {0}, but XML contains {1}.", typeSerializer.TargetType, type));
typeSerializer = this.Serializer.GetTypeSerializer(type);
this.Xml.MoveToElement();
}
else if (typeSerializer.TargetType == typeof(object))
{
throw ExceptionHelper.CreateInvalidContentException(this.Xml, this.BasePath, null, "XML is missing a \"Type\" attribute.");
}
}
return this.ReadRawObjectInternal(format, typeSerializer, existingInstance);
}
///
/// Reads a single object from the input XML stream as an instance of the specified type, optionally specifying an existing instance to receive the data.
///
///
/// The format of the XML.
///
///
///
/// Will be triggered if a multidimensional array is passed.
/// Will be thrown if no parameterless constructor for the corresponding can be found.
///
public T ReadRawObject(ContentSerializerAttribute format)
{
return this.ReadRawObjectInternal(format, this.Serializer.GetTypeSerializer(typeof(T)), null);
}
///
/// Reads a single object from the input XML stream, as an instance of the specified type.
///
///
/// The format of the XML.
/// The object receiving the data, or null if a new instance should be created.
///
///
///
/// Will be triggered if a multidimensional array is passed.
/// Will be thrown if no parameterless constructor for the corresponding can be found.
///
public T ReadRawObject(ContentSerializerAttribute format, T existingInstance)
{
return this.ReadRawObjectInternal(format, this.Serializer.GetTypeSerializer(typeof(T)), existingInstance);
}
///
/// Reads a single object from the input XML stream as an instance of the specified type using the specified type hint.
///
///
/// The format of the XML.
/// The type serializer.
///
///
///
///
public T ReadRawObject(ContentSerializerAttribute format, ContentTypeSerializer typeSerializer)
{
return this.ReadRawObjectInternal(format, typeSerializer, null);
}
///
/// Reads a single object from the input XML stream as an instance of the specified type using the specified type hint, optionally specifying an existing instance to receive the data.
///
///
/// The format of the XML.
/// The type serializer.
/// The object receiving the data, or null if a new instance should be created.
///
///
///
///
public T ReadRawObject(ContentSerializerAttribute format, ContentTypeSerializer typeSerializer, T existingInstance)
{
return this.ReadRawObjectInternal(format, typeSerializer, existingInstance);
}
private T ReadRawObjectInternal(ContentSerializerAttribute format, ContentTypeSerializer typeSerializer, object existingInstance)
{
if (typeSerializer == null)
throw new ArgumentNullException("typeSerializer");
if (format == null)
throw new ArgumentNullException("format");
object obj;
if (format.FlattenContent)
{
this.Xml.MoveToContent();
obj = typeSerializer.Deserialize(this, format, existingInstance);
}
else
{
if (!this.Xml.CheckForElement(format.ElementName))
throw ExceptionHelper.CreateInvalidContentException(this.Xml, this.BasePath, null, string.Format("XML element \"{0}\" not found.", format.ElementName));
IntermediateXmlReader xmlReader = this.Xml;
//When reading an empty element, XNA was giving a fake reader where it doesn't matter what you do.
if (this.Xml.IsEmptyElement)
{
this.Xml = EmptyElementReader.Instance;
}
xmlReader.ReadStartElement();
obj = typeSerializer.Deserialize(this, format, existingInstance);
if (this.Xml == xmlReader)
{
this.Xml.ReadEndElement();
}
else
{
this.Xml = xmlReader;
}
}
if (obj == null)
throw ExceptionHelper.CreateInvalidContentException(this.Xml, this.BasePath, null, string.Format("Intermediate ContentTypeSerializer {0} (handling type {1}) returned a null value from its Deserialize method.", typeSerializer.GetType(), typeSerializer.TargetType));
if (existingInstance != null && obj != existingInstance)
throw ExceptionHelper.CreateInvalidContentException(this.Xml, this.BasePath, null, string.Format("Intermediate ContentTypeSerializer {0} (handling type {1}) returned a new object instance from its Deserialize method. This should have loaded data into the existingInstance parameter.", typeSerializer.GetType(), typeSerializer.TargetType));
if (!(obj is T))
throw ExceptionHelper.CreateInvalidContentException(this.Xml, this.BasePath, null, string.Format("XML \"Type\" attribute is invalid. Expecting a subclass of {0}, but XML contains {1}.", typeof(T), obj.GetType()));
return (T)obj;
}
///
/// Reads a shared resource ID and records it for subsequent operations.
///
///
/// The format of the XML.
/// The fixup operation to perform.
///
///
public void ReadSharedResource(ContentSerializerAttribute format, Action fixup)
{
if (format == null)
throw new ArgumentNullException("format");
if (fixup == null)
throw new ArgumentNullException("fixup");
string text;
if (format.FlattenContent)
{
text = this.Xml.ReadContentAsString();
}
else
{
if (!this.Xml.CheckForElement(format.ElementName))
throw ExceptionHelper.CreateInvalidContentException(this.Xml, this.BasePath, null, string.Format("XML element \"{0}\" not found.", format.ElementName));
text = this.Xml.ReadElementContentAsString();
}
if (string.IsNullOrEmpty(text))
{
if (!format.AllowNull)
throw ExceptionHelper.CreateInvalidContentException(this.Xml, this.BasePath, null, string.Format("XML element \"{0}\" is not allowed to be null.", format.ElementName));
}
else
{
if (!this.sharedResourceFixups.ContainsKey(text))
{
this.sharedResourceFixups.Add(text, new List>());
}
this.sharedResourceFixups[text].Add((value) =>
{
if (!(value is T))
throw ExceptionHelper.CreateInvalidContentException(this.Xml, this.BasePath, null, string.Format("XML specifies invalid type for shared resource. Expecting a subclass of \"{0}\", but XML contains \"{1}\".", typeof(T), value.GetType()));
fixup((T)value);
});
}
}
internal void ReadSharedResources()
{
if (this.Xml.CheckForElement("Resources"))
{
Dictionary dictionary = new Dictionary();
ContentSerializerAttribute contentSerializerAttribute = new ContentSerializerAttribute();
contentSerializerAttribute.ElementName = "Resource";
this.Xml.ReadStartElement();
while (this.Xml.CheckForElement("Resource"))
{
string id = this.Xml.GetAttribute("ID");
if (string.IsNullOrEmpty(id))
throw ExceptionHelper.CreateInvalidContentException(this.Xml, this.BasePath, null, string.Format("XML attribute \"{0}\" was not found.", "ID"));
if (dictionary.ContainsKey(id))
throw ExceptionHelper.CreateInvalidContentException(this.Xml, this.BasePath, null, string.Format("Duplicate XML ID attribute \"{0}\".", id));
object value = this.ReadObject