2 // XsdDataContractExporter.cs
5 // Atsushi Enomoto <atsushi@ximian.com>
7 // Copyright (C) 2010 Novell, Inc. http://www.novell.com
9 // Permission is hereby granted, free of charge, to any person obtaining
10 // a copy of this software and associated documentation files (the
11 // "Software"), to deal in the Software without restriction, including
12 // without limitation the rights to use, copy, modify, merge, publish,
13 // distribute, sublicense, and/or sell copies of the Software, and to
14 // permit persons to whom the Software is furnished to do so, subject to
15 // the following conditions:
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30 using System
.CodeDom
.Compiler
;
31 using System
.Collections
;
32 using System
.Collections
.Generic
;
35 using System
.Reflection
;
37 using System
.Xml
.Schema
;
38 using System
.Xml
.Serialization
;
40 using QName
= System
.Xml
.XmlQualifiedName
;
43 // .NET exports almost empty schema for "http://www.w3.org/2001/XMLSchema" that
44 // contains only "schema" element which consists of a complexType with empty
45 // definition (i.e. <complexType/> ).
48 namespace System
.Runtime
.Serialization
50 static class TypeExtension
52 public static T GetCustomAttribute
<T
> (this MemberInfo mi
, bool inherit
)
54 foreach (T att
in mi
.GetCustomAttributes (typeof (T
), inherit
))
60 public class XsdDataContractExporter
64 public Type ClrType { get; set; }
65 public QName RootElementName { get; set; }
66 public XmlSchemaType SchemaType { get; set; }
67 public QName SchemaTypeName { get; set; }
70 static readonly List
<TypeImportInfo
> predefined_types
;
72 static XsdDataContractExporter ()
74 var l
= new List
<TypeImportInfo
> ();
76 if (!MSTypesSchema
.IsCompiled
)
77 MSTypesSchema
.Compile (null);
78 foreach (XmlSchemaElement el
in MSTypesSchema
.Elements
.Values
) {
79 var typeName
= el
.ElementSchemaType
.QualifiedName
;
80 var info
= new TypeImportInfo () {
81 RootElementName
= el
.QualifiedName
,
82 SchemaType
= typeName
.Namespace
== XmlSchema
.Namespace
? null : el
.ElementSchemaType
,
83 SchemaTypeName
= typeName
,
84 ClrType
= GetPredefinedTypeFromQName (typeName
) };
89 static Type
GetPredefinedTypeFromQName (QName qname
)
91 switch (qname
.Namespace
) {
92 case XmlSchema
.Namespace
:
93 return KnownTypeCollection
.GetPrimitiveTypeFromName (qname
.Name
);
94 case KnownTypeCollection
.MSSimpleNamespace
:
99 return typeof (TimeSpan
);
101 return typeof (Guid
);
105 throw new Exception ("Should not happen");
108 static XmlSchema mstypes_schema
;
109 static XmlSchema MSTypesSchema
{
111 if (mstypes_schema
== null) {
112 Assembly a
= Assembly
.GetCallingAssembly ();
113 Stream s
= a
.GetManifestResourceStream ("mstypes.schema");
114 mstypes_schema
= XmlSchema
.Read (s
, null);
116 return mstypes_schema
;
120 KnownTypeCollection known_types
= new KnownTypeCollection ();
121 List
<TypeImportInfo
> imported_types
= new List
<TypeImportInfo
> ();
123 public XsdDataContractExporter ()
124 : this (new XmlSchemaSet ())
128 public XsdDataContractExporter (XmlSchemaSet schemas
)
131 throw new ArgumentNullException ("schemas");
132 #if false // by default this is the only added schema. But it is pointless...
133 var xs
= new XmlSchema () { TargetNamespace = XmlSchema.Namespace }
;
134 xs
.Items
.Add (new XmlSchemaElement () { Name = "schema", SchemaType = new XmlSchemaComplexType () }
);
136 #else // FIXME: it is added only when the included items are in use.
137 schemas
.Add (MSTypesSchema
);
142 public ExportOptions Options { get; set; }
143 public XmlSchemaSet Schemas { get; private set; }
145 // CanExport implementation
147 public bool CanExport (ICollection
<Assembly
> assemblies
)
149 if (assemblies
== null)
150 throw new ArgumentNullException ("assemblies");
151 foreach (var ass
in assemblies
)
152 if (!CanExport (ass
.GetTypes ()))
157 public bool CanExport (ICollection
<Type
> types
)
160 throw new ArgumentNullException ("types");
161 foreach (var type
in types
)
162 if (!CanExport (type
))
167 public bool CanExport (Type type
)
170 throw new ArgumentNullException ("type");
172 if (predefined_types
.FirstOrDefault (i
=> i
.ClrType
== type
) != null)
175 known_types
.TryRegister (type
);
176 return known_types
.FindUserMap (type
) != null;
179 // Export implementation
181 public void Export (ICollection
<Assembly
> assemblies
)
183 if (assemblies
== null)
184 throw new ArgumentNullException ("assemblies");
185 foreach (var ass
in assemblies
)
186 Export (ass
.GetTypes ());
189 public void Export (ICollection
<Type
> types
)
192 throw new ArgumentNullException ("types");
193 foreach (var type
in types
)
197 public void Export (Type type
)
199 if (ExportCore (type
, true)) {
200 // This reprocess is required to clean up compilation state.
201 foreach (XmlSchema xs
in Schemas
.Schemas ())
202 Schemas
.Reprocess (xs
);
207 // returns true if it requires recompilcation
208 bool ExportCore (Type type
, bool rejectNonContract
)
211 throw new ArgumentNullException ("type");
213 if (predefined_types
.FirstOrDefault (i
=> i
.ClrType
== type
) != null) {
214 if (Schemas
.Contains (MSTypesSchema
.TargetNamespace
))
215 return false; // exists
216 Schemas
.Add (MSTypesSchema
);
219 if (imported_types
.FirstOrDefault (i
=> i
.ClrType
== type
) != null)
222 known_types
.TryRegister (type
);
223 var map
= known_types
.FindUserMap (type
);
226 map
.ExportSchemaType (this);
230 internal void ExportDictionaryContractType (CollectionDataContractAttribute attr
, Type type
, Type dicType
)
232 var qname
= GetSchemaTypeName (type
);
234 var typeArgs
= dicType
.IsGenericType
? dicType
.GetGenericArguments () : null;
235 var keyType
= typeArgs
!= null ? typeArgs
[0] : typeof (object);
236 var valueType
= typeArgs
!= null ? typeArgs
[1] : typeof (object);
237 ExportCore (keyType
, false);
238 ExportCore (valueType
, false);
240 string keyName
= "Key", valueName
= "Value";
242 keyName
= attr
.KeyName
?? keyName
;
243 valueName
= attr
.ValueName
?? valueName
;
245 string itemName
= attr
!= null && attr
.ItemName
!= null ? attr
.ItemName
: "KeyValueOf" + keyName
+ valueName
;
247 var ct
= CreateComplexType (qname
, type
);
248 var appInfo
= new XmlSchemaAppInfo ();
249 var node
= new XmlDocument ().CreateElement ("IsDictionary", KnownTypeCollection
.MSSimpleNamespace
);
250 node
.InnerText
= "true";
251 appInfo
.Markup
= new XmlNode
[] { node }
;
252 ct
.Annotation
= new XmlSchemaAnnotation ();
253 ct
.Annotation
.Items
.Add (appInfo
);
255 var seq
= new XmlSchemaSequence ();
257 var el
= new XmlSchemaElement () { Name = itemName, MinOccurs = 0, MaxOccursString = "unbounded" }
;
260 var dictType
= new XmlSchemaComplexType ();
261 el
.SchemaType
= dictType
;
262 var dictSeq
= new XmlSchemaSequence ();
263 dictType
.Particle
= dictSeq
;
264 dictSeq
.Items
.Add (new XmlSchemaElement () { Name = keyName, SchemaTypeName = GetSchemaTypeName (keyType), IsNillable = true }
);
265 dictSeq
.Items
.Add (new XmlSchemaElement () { Name = valueName, SchemaTypeName = GetSchemaTypeName (valueType), IsNillable = true }
);
268 internal void ExportListContractType (CollectionDataContractAttribute attr
, Type type
)
270 var qname
= attr
!= null && attr
.Name
!= null ? new QName (attr
.Name
, attr
.Namespace
?? GetXmlNamespace (type
)) : GetSchemaTypeName (type
);
272 var typeArgs
= type
.IsGenericType
? type
.GetGenericArguments () : null;
273 if (typeArgs
!= null && typeArgs
.Length
!= 1)
274 throw new InvalidDataContractException ("CollectionDataContractAttribute is applied to non-collection type.");
276 var itemType
= typeArgs
!= null ? typeArgs
[0] : type
.IsArray
? type
.GetElementType () : typeof (object);
277 bool nullable
= !itemType
.IsValueType
;
278 if (itemType
.IsGenericType
&& itemType
.GetGenericTypeDefinition () == typeof (Nullable
<>)) {
279 itemType
= itemType
.GetGenericArguments () [0];
282 ExportCore (itemType
, false);
284 var itemQName
= GetSchemaTypeName (itemType
);
285 var itemName
= attr
!= null && attr
.ItemName
!= null ? attr
.ItemName
: itemQName
.Name
;
287 var ct
= CreateComplexType (qname
, type
);
288 var seq
= new XmlSchemaSequence ();
290 var el
= new XmlSchemaElement () { Name = itemName, MinOccurs = 0, MaxOccursString = "unbounded", SchemaTypeName = itemQName, IsNillable = nullable }
;
294 var arrayType = new XmlSchemaComplexType ();
295 el.SchemaType = arrayType;
296 var arraySeq = new XmlSchemaSequence ();
297 arrayType.Particle = arraySeq;
298 arraySeq.Items.Add (new XmlSchemaElement () { Name = itemName, SchemaTypeName = itemQName, IsNillable = true });
302 internal void ExportEnumContractType (DataContractAttribute attr
, Type type
)
304 var qname
= attr
!= null && attr
.Name
!= null ? new QName (attr
.Name
, attr
.Namespace
?? GetXmlNamespace (type
)) : GetSchemaTypeName (type
);
305 var st
= CreateSimpleType (qname
, type
);
306 if (type
.GetCustomAttribute
<FlagsAttribute
> (false) != null) {
307 var list
= new XmlSchemaSimpleTypeList ();
308 var sct
= new XmlSchemaSimpleType ();
309 sct
.Content
= CreateEnumMembers (type
, attr
!= null);
314 st
.Content
= CreateEnumMembers (type
, attr
!= null);
317 XmlSchemaSimpleTypeRestriction
CreateEnumMembers (Type type
, bool expectAttribute
)
319 var r
= new XmlSchemaSimpleTypeRestriction () { BaseTypeName = GetSchemaTypeName (typeof (string)) }
;
320 foreach (var mi
in type
.GetFields (BindingFlags
.Public
| BindingFlags
.Static
)) {
321 var ema
= expectAttribute
? mi
.GetCustomAttribute
<EnumMemberAttribute
> (false) : null;
322 if (expectAttribute
&& ema
== null)
324 var xe
= new XmlSchemaEnumerationFacet () { Value = ema != null && ema.Value != null ? ema.Value : mi.Name }
;
330 internal void ExportStandardComplexType (DataContractAttribute attr
, Type type
, List
<DataMemberInfo
> members
)
332 var qname
= attr
!= null && attr
.Name
!= null ? new QName (attr
.Name
, attr
.Namespace
?? GetXmlNamespace (type
)) : GetSchemaTypeName (type
);
333 var ct
= CreateComplexType (qname
, type
);
335 if (type
.BaseType
!= null && type
.BaseType
!= typeof (object)) {
336 ExportCore (type
.BaseType
, false);
337 var xcc
= new XmlSchemaComplexContent ();
338 ct
.ContentModel
= xcc
;
339 var xcce
= new XmlSchemaComplexContentExtension ();
341 xcce
.BaseTypeName
= GetSchemaTypeName (type
.BaseType
);
342 xcce
.Particle
= CreateMembersSequence (type
, members
, attr
!= null);
345 ct
.Particle
= CreateMembersSequence (type
, members
, attr
!= null);
348 XmlSchemaSimpleType
CreateSimpleType (QName qname
, Type type
)
350 var xs
= GetSchema (qname
.Namespace
);
352 var el
= new XmlSchemaElement () { Name = qname.Name, IsNillable = true }
;
353 el
.SchemaTypeName
= qname
;
355 var st
= new XmlSchemaSimpleType () { Name = qname.Name }
;
357 imported_types
.Add (new TypeImportInfo () { RootElementName = qname, SchemaType = st, SchemaTypeName = qname, ClrType = type }
);
362 XmlSchemaComplexType
CreateComplexType (QName qname
, Type type
)
364 var xs
= GetSchema (qname
.Namespace
);
366 var el
= new XmlSchemaElement () { Name = qname.Name, IsNillable = true }
;
367 el
.SchemaTypeName
= qname
;
369 var ct
= new XmlSchemaComplexType () { Name = qname.Name }
;
371 imported_types
.Add (new TypeImportInfo () { RootElementName = qname, SchemaType = ct, SchemaTypeName = qname, ClrType = type }
);
376 static int CompareMembers (MemberInfo m1
, MemberInfo m2
)
378 var a1
= m1
.GetCustomAttribute
<DataMemberAttribute
> (false);
379 var a2
= m2
.GetCustomAttribute
<DataMemberAttribute
> (false);
380 return a1
.Order
== a2
.Order
? String
.CompareOrdinal (a1
.Name
?? m1
.Name
, a2
.Name
?? m2
.Name
) : a1
.Order
- a2
.Order
;
383 // FIXME: use members parameter to determine which members are to be exported.
384 XmlSchemaSequence
CreateMembersSequence (Type type
, List
<DataMemberInfo
> dataMembers
, bool expectContract
)
386 var seq
= new XmlSchemaSequence ();
387 var members
= new List
<MemberInfo
> ();
388 var flags
= BindingFlags
.Public
| BindingFlags
.Instance
| BindingFlags
.DeclaredOnly
;
390 flags
|= BindingFlags
.NonPublic
;
392 foreach (var mi
in type
.GetFields (flags
))
393 if (!expectContract
|| mi
.GetCustomAttribute
<DataMemberAttribute
> (false) != null)
395 foreach (var mi
in type
.GetProperties (flags
))
396 if ((!expectContract
|| mi
.GetCustomAttribute
<DataMemberAttribute
> (false) != null) && mi
.GetIndexParameters ().Length
== 0)
400 members
.Sort (CompareMembers
);
402 foreach (var mi
in members
) {
403 var dma
= mi
.GetCustomAttribute
<DataMemberAttribute
> (false);
404 var fi
= mi
as FieldInfo
;
405 var pi
= mi
as PropertyInfo
;
406 var mt
= fi
!= null ? fi
.FieldType
: pi
.PropertyType
;
407 bool nullable
= !mt
.IsValueType
;
408 if (mt
.IsGenericType
&& mt
.GetGenericTypeDefinition () == typeof (Nullable
<>)) {
409 mt
= mt
.GetGenericArguments () [0];
412 ExportCore (mt
, false);
414 var name
= dma
!= null && dma
.Name
!= null ? dma
.Name
: mi
.Name
;
415 var xe
= new XmlSchemaElement () { Name = name, IsNillable = nullable }
;
416 xe
.SchemaTypeName
= GetSchemaTypeName (mt
);
422 XmlSchema
GetSchema (string ns
)
424 foreach (XmlSchema xs
in Schemas
.Schemas (ns
))
426 var nxs
= new XmlSchema () { ElementFormDefault = XmlSchemaForm.Qualified }
;
427 if (!String
.IsNullOrEmpty (ns
))
428 nxs
.TargetNamespace
= ns
;
433 string GetXmlTypeName (Type type
)
435 var qname
= KnownTypeCollection
.GetPrimitiveTypeName (type
);
436 return qname
.Equals (QName
.Empty
) ? type
.Name
: qname
.Name
;
439 string GetXmlNamespace (Type type
)
441 foreach (ContractNamespaceAttribute a
in type
.Assembly
.GetCustomAttributes (typeof (ContractNamespaceAttribute
), false))
442 if (a
.ClrNamespace
== type
.Namespace
)
443 return a
.ContractNamespace
;
444 return KnownTypeCollection
.DefaultClrNamespaceBase
+ type
.Namespace
;
447 // get mapping info (either exported or predefined).
449 public QName
GetRootElementName (Type type
)
451 var info
= predefined_types
.FirstOrDefault (i
=> i
.ClrType
== type
);
453 return info
.RootElementName
;
454 info
= imported_types
.FirstOrDefault (i
=> i
.ClrType
== type
);
455 if (info
!= null && info
.RootElementName
!= null)
456 return info
.RootElementName
;
458 return GetSchemaTypeName (type
);
461 public XmlSchemaType
GetSchemaType (Type type
)
463 var info
= predefined_types
.FirstOrDefault (i
=> i
.ClrType
== type
);
465 return info
.SchemaType
;
466 info
= imported_types
.FirstOrDefault (i
=> i
.ClrType
== type
);
468 return info
.SchemaType
;
473 public QName
GetSchemaTypeName (Type type
)
475 var info
= predefined_types
.FirstOrDefault (i
=> i
.ClrType
== type
);
477 return info
.SchemaTypeName
;
478 info
= imported_types
.FirstOrDefault (i
=> i
.ClrType
== type
);
479 if (info
!= null && info
.SchemaTypeName
!= null)
480 return info
.SchemaTypeName
;
482 var cdca
= type
.GetCustomAttribute
<CollectionDataContractAttribute
> (false);
484 return new QName (cdca
.Name
?? GetXmlTypeName (type
), cdca
.Namespace
?? GetXmlNamespace (type
));
485 var dca
= type
.GetCustomAttribute
<DataContractAttribute
> (false);
487 return new QName (dca
.Name
?? GetXmlTypeName (type
), dca
.Namespace
?? GetXmlNamespace (type
));
490 var item
= GetSchemaTypeName (type
.GetElementType ());
491 if (item
.Namespace
== XmlSchema
.Namespace
)
492 return new QName ("ArrayOf" + item
.Name
, KnownTypeCollection
.MSArraysNamespace
);
493 return new QName ("ArrayOf" + item
.Name
, item
.Namespace
);
496 return new QName (type
.Name
, KnownTypeCollection
.DefaultClrNamespaceBase
+ type
.Namespace
);