// // XsdDataContractExporter.cs // // Author: // Atsushi Enomoto // // Copyright (C) 2010 Novell, Inc. http://www.novell.com // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // using System; using System.CodeDom; using System.CodeDom.Compiler; using System.Collections; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using System.Xml; using System.Xml.Schema; using System.Xml.Serialization; using QName = System.Xml.XmlQualifiedName; // // .NET exports almost empty schema for "http://www.w3.org/2001/XMLSchema" that // contains only "schema" element which consists of a complexType with empty // definition (i.e. ). // namespace System.Runtime.Serialization { static class TypeExtension { public static T GetCustomAttribute (this MemberInfo mi, bool inherit) { foreach (T att in mi.GetCustomAttributes (typeof (T), inherit)) return att; return default (T); } } public class XsdDataContractExporter { class TypeImportInfo { public Type ClrType { get; set; } public QName RootElementName { get; set; } public XmlSchemaType SchemaType { get; set; } public QName SchemaTypeName { get; set; } } static readonly List predefined_types; static XsdDataContractExporter () { var l = new List (); predefined_types = l; if (!MSTypesSchema.IsCompiled) MSTypesSchema.Compile (null); foreach (XmlSchemaElement el in MSTypesSchema.Elements.Values) { var typeName = el.ElementSchemaType.QualifiedName; var info = new TypeImportInfo () { RootElementName = el.QualifiedName, SchemaType = typeName.Namespace == XmlSchema.Namespace ? null : el.ElementSchemaType, SchemaTypeName = typeName, ClrType = GetPredefinedTypeFromQName (typeName) }; l.Add (info); } } static Type GetPredefinedTypeFromQName (QName qname) { switch (qname.Namespace) { case XmlSchema.Namespace: return KnownTypeCollection.GetPrimitiveTypeFromName (qname.Name); case KnownTypeCollection.MSSimpleNamespace: switch (qname.Name) { case "char": return typeof (char); case "duration": return typeof (TimeSpan); case "guid": return typeof (Guid); } break; } throw new Exception ("Should not happen"); } static XmlSchema mstypes_schema; static XmlSchema MSTypesSchema { get { if (mstypes_schema == null) { Assembly a = Assembly.GetCallingAssembly (); Stream s = a.GetManifestResourceStream ("mstypes.schema"); mstypes_schema= XmlSchema.Read (s, null); } return mstypes_schema; } } KnownTypeCollection known_types = new KnownTypeCollection (); List imported_types = new List (); public XsdDataContractExporter () : this (new XmlSchemaSet ()) { } public XsdDataContractExporter (XmlSchemaSet schemas) { if (schemas == null) throw new ArgumentNullException ("schemas"); #if false // by default this is the only added schema. But it is pointless... var xs = new XmlSchema () { TargetNamespace = XmlSchema.Namespace }; xs.Items.Add (new XmlSchemaElement () { Name = "schema", SchemaType = new XmlSchemaComplexType () }); schemas.Add (xs); #else // FIXME: it is added only when the included items are in use. schemas.Add (MSTypesSchema); #endif Schemas = schemas; } public ExportOptions Options { get; set; } public XmlSchemaSet Schemas { get; private set; } // CanExport implementation public bool CanExport (ICollection assemblies) { if (assemblies == null) throw new ArgumentNullException ("assemblies"); foreach (var ass in assemblies) if (!CanExport (ass.GetTypes ())) return false; return true; } public bool CanExport (ICollection types) { if (types == null) throw new ArgumentNullException ("types"); foreach (var type in types) if (!CanExport (type)) return false; return true; } public bool CanExport (Type type) { if (type == null) throw new ArgumentNullException ("type"); if (predefined_types.FirstOrDefault (i => i.ClrType == type) != null) return true; known_types.TryRegister (type); return known_types.FindUserMap (type) != null; } // Export implementation public void Export (ICollection assemblies) { if (assemblies == null) throw new ArgumentNullException ("assemblies"); foreach (var ass in assemblies) Export (ass.GetTypes ()); } public void Export (ICollection types) { if (types == null) throw new ArgumentNullException ("types"); foreach (var type in types) Export (type); } public void Export (Type type) { if (ExportCore (type, true)) { // This reprocess is required to clean up compilation state. foreach (XmlSchema xs in Schemas.Schemas ()) Schemas.Reprocess (xs); Schemas.Compile (); } } // returns true if it requires recompilcation bool ExportCore (Type type, bool rejectNonContract) { if (type == null) throw new ArgumentNullException ("type"); if (predefined_types.FirstOrDefault (i => i.ClrType == type) != null) { if (Schemas.Contains (MSTypesSchema.TargetNamespace)) return false; // exists Schemas.Add (MSTypesSchema); return false; } if (imported_types.FirstOrDefault (i => i.ClrType == type) != null) return false; known_types.TryRegister (type); var map = known_types.FindUserMap (type); if (map == null) return false; map.ExportSchemaType (this); return true; } internal void ExportDictionaryContractType (CollectionDataContractAttribute attr, Type type, Type dicType) { var qname = GetSchemaTypeName (type); var typeArgs = dicType.IsGenericType ? dicType.GetGenericArguments () : null; var keyType = typeArgs != null ? typeArgs [0] : typeof (object); var valueType = typeArgs != null ? typeArgs [1] : typeof (object); ExportCore (keyType, false); ExportCore (valueType, false); string keyName = "Key", valueName = "Value"; if (attr != null) { keyName = attr.KeyName ?? keyName; valueName = attr.ValueName ?? valueName; } string itemName = attr != null && attr.ItemName != null ? attr.ItemName : "KeyValueOf" + keyName + valueName; var ct = CreateComplexType (qname, type); var appInfo = new XmlSchemaAppInfo (); var node = new XmlDocument ().CreateElement ("IsDictionary", KnownTypeCollection.MSSimpleNamespace); node.InnerText = "true"; appInfo.Markup = new XmlNode [] { node }; ct.Annotation = new XmlSchemaAnnotation (); ct.Annotation.Items.Add (appInfo); var seq = new XmlSchemaSequence (); ct.Particle = seq; var el = new XmlSchemaElement () { Name = itemName, MinOccurs = 0, MaxOccursString = "unbounded" }; seq.Items.Add (el); var dictType = new XmlSchemaComplexType (); el.SchemaType = dictType; var dictSeq = new XmlSchemaSequence (); dictType.Particle = dictSeq; dictSeq.Items.Add (new XmlSchemaElement () { Name = keyName, SchemaTypeName = GetSchemaTypeName (keyType), IsNillable = true }); dictSeq.Items.Add (new XmlSchemaElement () { Name = valueName, SchemaTypeName = GetSchemaTypeName (valueType), IsNillable = true }); } internal void ExportListContractType (CollectionDataContractAttribute attr, Type type) { var qname = attr != null && attr.Name != null ? new QName (attr.Name, attr.Namespace ?? GetXmlNamespace (type)) : GetSchemaTypeName (type); var typeArgs = type.IsGenericType ? type.GetGenericArguments () : null; if (typeArgs != null && typeArgs.Length != 1) throw new InvalidDataContractException ("CollectionDataContractAttribute is applied to non-collection type."); var itemType = typeArgs != null ? typeArgs [0] : type.IsArray ? type.GetElementType () : typeof (object); bool nullable = !itemType.IsValueType; if (itemType.IsGenericType && itemType.GetGenericTypeDefinition () == typeof (Nullable<>)) { itemType = itemType.GetGenericArguments () [0]; nullable = true; } ExportCore (itemType, false); var itemQName = GetSchemaTypeName (itemType); var itemName = attr != null && attr.ItemName != null ? attr.ItemName : itemQName.Name; var ct = CreateComplexType (qname, type); var seq = new XmlSchemaSequence (); ct.Particle = seq; var el = new XmlSchemaElement () { Name = itemName, MinOccurs = 0, MaxOccursString = "unbounded", SchemaTypeName = itemQName, IsNillable = nullable }; seq.Items.Add (el); /* var arrayType = new XmlSchemaComplexType (); el.SchemaType = arrayType; var arraySeq = new XmlSchemaSequence (); arrayType.Particle = arraySeq; arraySeq.Items.Add (new XmlSchemaElement () { Name = itemName, SchemaTypeName = itemQName, IsNillable = true }); */ } internal void ExportEnumContractType (DataContractAttribute attr, Type type) { var qname = attr != null && attr.Name != null ? new QName (attr.Name, attr.Namespace ?? GetXmlNamespace (type)) : GetSchemaTypeName (type); var st = CreateSimpleType (qname, type); if (type.GetCustomAttribute (false) != null) { var list = new XmlSchemaSimpleTypeList (); var sct = new XmlSchemaSimpleType (); sct.Content = CreateEnumMembers (type, attr != null); list.ItemType = sct; st.Content = list; } else st.Content = CreateEnumMembers (type, attr != null); } XmlSchemaSimpleTypeRestriction CreateEnumMembers (Type type, bool expectAttribute) { var r = new XmlSchemaSimpleTypeRestriction () { BaseTypeName = GetSchemaTypeName (typeof (string)) }; foreach (var mi in type.GetFields (BindingFlags.Public | BindingFlags.Static)) { var ema = expectAttribute ? mi.GetCustomAttribute (false) : null; if (expectAttribute && ema == null) continue; var xe = new XmlSchemaEnumerationFacet () { Value = ema != null && ema.Value != null ? ema.Value : mi.Name }; r.Facets.Add (xe); } return r; } internal void ExportStandardComplexType (DataContractAttribute attr, Type type, List members) { var qname = attr != null && attr.Name != null ? new QName (attr.Name, attr.Namespace ?? GetXmlNamespace (type)) : GetSchemaTypeName (type); var ct = CreateComplexType (qname, type); if (type.BaseType != null && type.BaseType != typeof (object)) { ExportCore (type.BaseType, false); var xcc = new XmlSchemaComplexContent (); ct.ContentModel = xcc; var xcce = new XmlSchemaComplexContentExtension (); xcc.Content = xcce; xcce.BaseTypeName = GetSchemaTypeName (type.BaseType); xcce.Particle = CreateMembersSequence (type, members, attr != null); } else ct.Particle = CreateMembersSequence (type, members, attr != null); } XmlSchemaSimpleType CreateSimpleType (QName qname, Type type) { var xs = GetSchema (qname.Namespace); var el = new XmlSchemaElement () { Name = qname.Name, IsNillable = true }; el.SchemaTypeName = qname; xs.Items.Add (el); var st = new XmlSchemaSimpleType () { Name = qname.Name }; xs.Items.Add (st); imported_types.Add (new TypeImportInfo () { RootElementName = qname, SchemaType = st, SchemaTypeName = qname, ClrType = type }); return st; } XmlSchemaComplexType CreateComplexType (QName qname, Type type) { var xs = GetSchema (qname.Namespace); var el = new XmlSchemaElement () { Name = qname.Name, IsNillable = true }; el.SchemaTypeName = qname; xs.Items.Add (el); var ct = new XmlSchemaComplexType () { Name = qname.Name }; xs.Items.Add (ct); imported_types.Add (new TypeImportInfo () { RootElementName = qname, SchemaType = ct, SchemaTypeName = qname, ClrType = type }); return ct; } static int CompareMembers (MemberInfo m1, MemberInfo m2) { var a1 = m1.GetCustomAttribute (false); var a2 = m2.GetCustomAttribute (false); return a1.Order == a2.Order ? String.CompareOrdinal (a1.Name ?? m1.Name, a2.Name ?? m2.Name) : a1.Order - a2.Order; } // FIXME: use members parameter to determine which members are to be exported. XmlSchemaSequence CreateMembersSequence (Type type, List dataMembers, bool expectContract) { var seq = new XmlSchemaSequence (); var members = new List (); var flags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly; if (expectContract) flags |= BindingFlags.NonPublic; foreach (var mi in type.GetFields (flags)) if (!expectContract || mi.GetCustomAttribute (false) != null) members.Add (mi); foreach (var mi in type.GetProperties (flags)) if ((!expectContract || mi.GetCustomAttribute (false) != null) && mi.GetIndexParameters ().Length == 0) members.Add (mi); if (expectContract) members.Sort (CompareMembers); foreach (var mi in members) { var dma = mi.GetCustomAttribute (false); var fi = mi as FieldInfo; var pi = mi as PropertyInfo; var mt = fi != null ? fi.FieldType : pi.PropertyType; bool nullable = !mt.IsValueType; if (mt.IsGenericType && mt.GetGenericTypeDefinition () == typeof (Nullable<>)) { mt = mt.GetGenericArguments () [0]; nullable = true; } ExportCore (mt, false); var name = dma != null && dma.Name != null ? dma.Name : mi.Name; var xe = new XmlSchemaElement () { Name = name, IsNillable = nullable }; xe.SchemaTypeName = GetSchemaTypeName (mt); seq.Items.Add (xe); } return seq; } XmlSchema GetSchema (string ns) { foreach (XmlSchema xs in Schemas.Schemas (ns)) return xs; var nxs = new XmlSchema () { ElementFormDefault = XmlSchemaForm.Qualified }; if (!String.IsNullOrEmpty (ns)) nxs.TargetNamespace = ns; Schemas.Add (nxs); return nxs; } string GetXmlTypeName (Type type) { var qname = KnownTypeCollection.GetPrimitiveTypeName (type); return qname.Equals (QName.Empty) ? type.Name : qname.Name; } string GetXmlNamespace (Type type) { foreach (ContractNamespaceAttribute a in type.Assembly.GetCustomAttributes (typeof (ContractNamespaceAttribute), false)) if (a.ClrNamespace == type.Namespace) return a.ContractNamespace; return KnownTypeCollection.DefaultClrNamespaceBase + type.Namespace; } // get mapping info (either exported or predefined). public QName GetRootElementName (Type type) { var info = predefined_types.FirstOrDefault (i => i.ClrType == type); if (info != null) return info.RootElementName; info = imported_types.FirstOrDefault (i => i.ClrType == type); if (info != null && info.RootElementName != null) return info.RootElementName; return GetSchemaTypeName (type); } public XmlSchemaType GetSchemaType (Type type) { var info = predefined_types.FirstOrDefault (i => i.ClrType == type); if (info != null) return info.SchemaType; info = imported_types.FirstOrDefault (i => i.ClrType == type); if (info != null) return info.SchemaType; return null; } public QName GetSchemaTypeName (Type type) { var info = predefined_types.FirstOrDefault (i => i.ClrType == type); if (info != null) return info.SchemaTypeName; info = imported_types.FirstOrDefault (i => i.ClrType == type); if (info != null && info.SchemaTypeName != null) return info.SchemaTypeName; var cdca = type.GetCustomAttribute (false); if (cdca != null) return new QName (cdca.Name ?? GetXmlTypeName (type), cdca.Namespace ?? GetXmlNamespace (type)); var dca = type.GetCustomAttribute (false); if (dca != null) return new QName (dca.Name ?? GetXmlTypeName (type), dca.Namespace ?? GetXmlNamespace (type)); if (type.IsArray) { var item = GetSchemaTypeName (type.GetElementType ()); if (item.Namespace == XmlSchema.Namespace) return new QName ("ArrayOf" + item.Name, KnownTypeCollection.MSArraysNamespace); return new QName ("ArrayOf" + item.Name, item.Namespace); } return new QName (type.Name, KnownTypeCollection.DefaultClrNamespaceBase + type.Namespace); } } }