#region Using Statements
using ANX.Framework.NonXNA.Development;
using System;
using System.Collections.Generic;
using System.Globalization;
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 including serialization and state tracking for shared resources and external references.
///
[Developer("KorsarNek")]
[TestState(TestStateAttribute.TestState.Tested)]
[PercentageComplete(100)]
public sealed class IntermediateWriter
{
private struct ExternalReference
{
public Type TargetType;
public string Filename;
public string ID;
}
private struct RecursionIdentifier
{
public object instance;
public Type targetType;
}
private string basePath;
private List writtenTypes = new List();
private Dictionary recurseDetector = new Dictionary();
private Dictionary sharedResourceNames = new Dictionary(new ReferenceEqualityComparer());
private Queue sharedResources = new Queue();
private List externalReferences = new List();
private IntermediateXmlWriter realXml;
private StringBuilder inMemoryXml = new StringBuilder();
private XmlTypeNameWriter xmlTypeNameWriter;
///
/// Gets the parent serializer.
///
public IntermediateSerializer Serializer
{
get;
private set;
}
///
/// Gets the XML output stream.
///
public IntermediateXmlWriter Xml
{
get;
private set;
}
internal IntermediateWriter(IntermediateSerializer serializer, IntermediateXmlWriter xmlWriter, string basePath, XmlTypeNameContainer xmlTypeNameContainer, bool shortenNamespaces)
{
this.Serializer = serializer;
this.realXml = xmlWriter;
this.xmlTypeNameWriter = new XmlTypeNameWriter(xmlTypeNameContainer, shortenNamespaces);
StringWriter memoryWriter = new StringWriter(inMemoryXml);
//The xml declaration will already be contained in the given xmlWriter.
this.Xml = new IntermediateXmlWriter(XmlWriter.Create(memoryWriter, new XmlWriterSettings() { OmitXmlDeclaration = true, ConformanceLevel = ConformanceLevel.Auto }));
//We need a full path to be able to make the compiled files relative to it.
if (basePath != null)
{
basePath = Path.GetFullPath(basePath);
}
this.basePath = basePath;
}
///
/// Writes a single object to the output XML stream.
///
///
/// The value to write.
/// The format of the XML.
public void WriteObject(T value, ContentSerializerAttribute format)
{
this.WriteObject(value, format, this.Serializer.GetTypeSerializer(typeof(T)));
}
///
/// Writes a single object to the output XML stream, using the specified type hint.
///
///
/// The value to write.
/// The format of the XML.
/// The type serializer.
public void WriteObject(T value, ContentSerializerAttribute format, ContentTypeSerializer typeSerializer)
{
if (typeSerializer == null)
throw new ArgumentNullException("typeSerializer");
if (format == null)
{
throw new ArgumentNullException("format");
}
if (!format.FlattenContent)
{
if (string.IsNullOrEmpty(format.ElementName))
throw new ArgumentException("ContentSerializerAttribute has a null ElementName property.");
this.Xml.WriteStartElement(format.ElementName);
}
if (value == null)
{
if (format.FlattenContent)
throw new InvalidOperationException("Cannot serialize null values when the ContentSerializerAttribute.FlattenContent flag is set.");
this.Xml.WriteAttributeString("Null", "true");
}
else
{
Type type = value.GetType();
if (type.IsSubclassOf(typeof(Type)))
{
type = typeof(Type);
}
//Special handling for nullables. Reason is that calling GetType() on a nullable will return the underlying type, not the nullable, therefore we would
//wrongly write the underlying type into the xml even though it is already known.
Type targetType = typeSerializer.TargetType;
if (targetType.IsGenericType && targetType.GetGenericTypeDefinition() == typeof(Nullable<>))
{
targetType = Nullable.GetUnderlyingType(targetType);
}
if (type != targetType)
{
if (format.FlattenContent)
throw new InvalidOperationException("Cannot serialize derived types when the ContentSerializerAttribute.FlattenContent flag is set.");
typeSerializer = this.Serializer.GetTypeSerializer(type);
this.Xml.WriteStartAttribute("Type");
this.WriteTypeName(typeSerializer.TargetType);
this.Xml.WriteEndAttribute();
}
RecursionIdentifier recursion = new RecursionIdentifier()
{
instance = value,
targetType = typeof(T)
};
if (this.recurseDetector.ContainsKey(recursion))
throw new InvalidOperationException(string.Format("Cyclic reference found while serializing {0}. You may be missing a ContentSerializerAttribute.SharedResource flag.", value));
this.recurseDetector.Add(recursion, true);
typeSerializer.Serialize(this, value, format);
this.recurseDetector.Remove(recursion);
writtenTypes.Add(typeSerializer.TargetType);
}
if (!format.FlattenContent)
{
this.Xml.WriteEndElement();
}
}
///
/// Writes a single object to the output XML stream using the specified serializer worker.
///
///
/// The value to write.
/// The format of the XML.
public void WriteRawObject(T value, ContentSerializerAttribute format)
{
if (value == null)
throw new ArgumentException("value");
this.WriteRawObject(value, format, this.Serializer.GetTypeSerializer(value.GetType()));
}
///
/// Writes a single object to the output XML stream as an instance of the specified type.
///
///
/// The value to write.
/// The format of the XML.
/// The type serializer.
public void WriteRawObject(T value, ContentSerializerAttribute format, ContentTypeSerializer typeSerializer)
{
if (value == null)
throw new ArgumentException("value");
if (typeSerializer == null)
throw new ArgumentNullException("typeSerializer");
if (format == null)
{
throw new ArgumentNullException("format");
}
if (!format.FlattenContent)
{
if (string.IsNullOrEmpty(format.ElementName))
{
throw new ArgumentException("\"format\" must have an ElementName if FlattenContent is false.");
}
this.Xml.WriteStartElement(format.ElementName);
}
typeSerializer.Serialize(this, value, format);
if (!format.FlattenContent)
{
this.Xml.WriteEndElement();
}
}
///
/// Adds a shared reference to the output XML and records the object to be serialized later.
///
///
/// The value to write.
/// The format of the XML.
public void WriteSharedResource(T value, ContentSerializerAttribute format)
{
if (format == null)
throw new ArgumentNullException("format");
if (!format.FlattenContent)
{
if (string.IsNullOrEmpty(format.ElementName))
throw new ArgumentException("ContentSerializerAttribute has a null ElementName property.");
this.Xml.WriteStartElement(format.ElementName);
}
if (value != null)
{
string text;
if (!this.sharedResourceNames.TryGetValue(value, out text))
{
text = "#Resource" + (this.sharedResourceNames.Count + 1).ToString(CultureInfo.InvariantCulture);
this.sharedResourceNames.Add(value, text);
this.sharedResources.Enqueue(value);
}
this.Xml.WriteString(text);
}
if (!format.FlattenContent)
{
this.Xml.WriteEndElement();
}
}
internal void WriteSharedResources()
{
if (this.sharedResources.Count > 0)
{
this.Xml.WriteStartElement("Resources");
ContentSerializerAttribute contentSerializerAttribute = new ContentSerializerAttribute();
contentSerializerAttribute.ElementName = "Resource";
contentSerializerAttribute.FlattenContent = true;
while (this.sharedResources.Count > 0)
{
object obj = this.sharedResources.Dequeue();
Type type = obj.GetType();
ContentTypeSerializer typeSerializer = this.Serializer.GetTypeSerializer(type);
this.Xml.WriteStartElement("Resource");
this.Xml.WriteAttributeString("ID", this.sharedResourceNames[obj]);
this.Xml.WriteStartAttribute("Type");
this.WriteTypeName(type);
this.Xml.WriteEndAttribute();
this.WriteRawObject(obj, contentSerializerAttribute, typeSerializer);
this.Xml.WriteEndElement();
}
this.Xml.WriteEndElement();
}
}
///
/// Adds an external reference to the output XML, and records the filename to be serialized later.
///
///
/// The external reference to add.
public void WriteExternalReference(ExternalReference value)
{
if (value == null || value.Filename == null)
{
return;
}
//TODO: It's currently not possible to reference files that are a directory higher, only that are lower than the current file or at the same level.
//Should think about making these paths relative with URIs which would allow reference files that are higher, but would always make a relative path if a basePath is given.
string relativePath = value.Filename;
if (relativePath != null && basePath != null && relativePath.StartsWith(basePath))
{
relativePath = relativePath.Substring(basePath.Length).Trim(Path.DirectorySeparatorChar);
}
string id = null;
foreach (IntermediateWriter.ExternalReference current in this.externalReferences)
{
if (current.TargetType == typeof(T) && current.Filename == relativePath)
{
id = current.ID;
break;
}
}
if (id == null)
{
id = "#External" + (this.externalReferences.Count + 1).ToString(CultureInfo.InvariantCulture);
IntermediateWriter.ExternalReference item;
item.TargetType = typeof(T);
item.Filename = relativePath;
item.ID = id;
this.externalReferences.Add(item);
}
this.Xml.WriteElementString("Reference", id);
}
internal void WriteExternalReferences()
{
if (this.externalReferences.Count > 0)
{
this.Xml.WriteStartElement("ExternalReferences");
foreach (IntermediateWriter.ExternalReference current in this.externalReferences)
{
this.Xml.WriteStartElement("ExternalReference");
this.Xml.WriteAttributeString("ID", current.ID);
this.Xml.WriteStartAttribute("TargetType");
this.WriteTypeName(current.TargetType);
this.Xml.WriteEndAttribute();
this.Xml.WriteString(current.Filename);
this.Xml.WriteEndElement();
}
this.Xml.WriteEndElement();
}
}
internal void WriteUsedNamespaces()
{
foreach (var xmlNamespace in this.xmlTypeNameWriter.NamespaceAbbreviations)
{
this.realXml.WriteAttributeString("xmlns", xmlNamespace.Value, null, xmlNamespace.Key);
}
}
internal void ComposeXml()
{
this.Xml.Flush();
this.realXml.WriteRaw(this.inMemoryXml.ToString());
}
///
/// Writes a managed type descriptor to the XML output stream.
///
/// The type.
public void WriteTypeName(Type type)
{
if (type == null)
throw new ArgumentNullException("type");
this.Xml.WriteString(this.xmlTypeNameWriter.getXmlTypeName(type));
}
}
}