2 // XsdDataContractImporter.cs
5 // Atsushi Enomoto <atsushi@ximian.com>
6 // Martin Baulig <martin.baulig@xamarin.com>
8 // Copyright (C) 2010 Novell, Inc. http://www.novell.com
11 // Permission is hereby granted, free of charge, to any person obtaining
12 // a copy of this software and associated documentation files (the
13 // "Software"), to deal in the Software without restriction, including
14 // without limitation the rights to use, copy, modify, merge, publish,
15 // distribute, sublicense, and/or sell copies of the Software, and to
16 // permit persons to whom the Software is furnished to do so, subject to
17 // the following conditions:
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
22 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
27 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
33 using System.CodeDom.Compiler;
34 using System.Collections;
35 using System.Collections.Generic;
38 using System.Reflection;
40 using System.Xml.Schema;
41 using System.Xml.Serialization;
43 using QName = System.Xml.XmlQualifiedName;
45 namespace System.Runtime.Serialization
47 [MonoTODO ("support arrays")]
48 public class XsdDataContractImporter
50 static readonly XmlQualifiedName qname_anytype = new XmlQualifiedName ("anyType", XmlSchema.Namespace);
52 public XsdDataContractImporter ()
57 public XsdDataContractImporter (CodeCompileUnit codeCompileUnit)
59 // null argument is ok.
60 CodeCompileUnit = codeCompileUnit ?? new CodeCompileUnit ();
62 // Options is null by default
65 public CodeCompileUnit CodeCompileUnit { get; private set; }
67 CodeDomProvider code_provider = CodeDomProvider.CreateProvider ("csharp");
68 Dictionary<CodeNamespace,CodeIdentifiers> identifiers_table = new Dictionary<CodeNamespace,CodeIdentifiers> ();
69 ImportOptions import_options;
71 public ImportOptions Options {
72 get { return import_options; }
74 import_options = value;
75 code_provider = value.CodeProvider ?? code_provider;
79 void GenerateXmlType (XmlQualifiedName qname)
81 var cns = GetCodeNamespace (qname.Namespace);
82 var td = new CodeTypeDeclaration () {
83 Name = GetUniqueName (CodeIdentifier.MakeValid (qname.Name), cns),
84 TypeAttributes = GenerateInternal ? TypeAttributes.NotPublic : TypeAttributes.Public,
87 td.BaseTypes.Add (new CodeTypeReference (typeof (IXmlSerializable)));
89 var thisNodes = new CodePropertyReferenceExpression (new CodeThisReferenceExpression (), "Nodes"); // property this.Nodes
90 var xmlSerializableServices = new CodeTypeReferenceExpression (typeof (XmlSerializableServices)); // static XmlSerializableServices.
91 var qnameType = new CodeTypeReference (typeof (XmlQualifiedName));
93 // XmlQualifiedName qname = new XmlQualifiedName ({qname.Name}, {qname.Namespace});
94 td.Members.Add (new CodeMemberField () { Name = "qname", Type = qnameType, InitExpression = new CodeObjectCreateExpression (qnameType, new CodePrimitiveExpression (qname.Name), new CodePrimitiveExpression (qname.Namespace)) });
96 // public XmlNode[] Nodes { get; set; }
97 td.Members.Add (new CodeMemberProperty () { Name = "Nodes", Type = new CodeTypeReference (typeof (XmlNode [])), Attributes = (GenerateInternal ? MemberAttributes.Assembly : MemberAttributes.Public) | MemberAttributes.Final, HasGet = true, HasSet = true });
99 // public void ReadXml(XmlReader reader) {
100 var read = new CodeMemberMethod () { Name = "ReadXml", Attributes = (GenerateInternal ? MemberAttributes.Assembly : MemberAttributes.Public) | MemberAttributes.Final };
101 read.Parameters.Add (new CodeParameterDeclarationExpression (new CodeTypeReference (typeof (XmlReader)), "reader"));
102 // this.Nodes = XmlSerializableServices.ReadXml(reader);
103 read.Statements.Add (
104 new CodeAssignStatement (thisNodes,
105 new CodeMethodInvokeExpression (
106 new CodeMethodReferenceExpression (xmlSerializableServices, "ReadXml"),
107 new CodeArgumentReferenceExpression ("reader"))));
109 td.Members.Add (read);
111 // public void WriteXml(XmlWriter writer) {
112 var write = new CodeMemberMethod () { Name = "WriteXml",Attributes = (GenerateInternal ? MemberAttributes.Assembly : MemberAttributes.Public) | MemberAttributes.Final };
113 write.Parameters.Add (new CodeParameterDeclarationExpression (new CodeTypeReference (typeof (XmlWriter)), "writer"));
114 // XmlSerializableServices.WriteXml(writer, this.Nodes);
115 write.Statements.Add (
116 new CodeMethodInvokeExpression (
117 new CodeMethodReferenceExpression (xmlSerializableServices, "WriteXml"),
118 new CodeArgumentReferenceExpression ("writer"),
121 td.Members.Add (write);
123 // public XmlSchema GetSchema () { return null; }
124 var getSchema = new CodeMemberMethod () { Name = "GetSchema", Attributes = (GenerateInternal ? MemberAttributes.Assembly : MemberAttributes.Public) | MemberAttributes.Final, ReturnType = new CodeTypeReference (typeof (XmlSchema)) };
125 getSchema.Statements.Add (new CodeMethodReturnStatement (new CodePrimitiveExpression (null)));
126 td.Members.Add (getSchema);
128 // public static XmlQualifiedName ExportSchema (XmlSchemaSet schemas) {
129 var export = new CodeMemberMethod () { Name = "ExportSchema", Attributes = (GenerateInternal ? MemberAttributes.Assembly : MemberAttributes.Public) | MemberAttributes.Final | MemberAttributes.Static, ReturnType = qnameType };
130 export.Parameters.Add (new CodeParameterDeclarationExpression (new CodeTypeReference (typeof (XmlSchemaSet)), "schemas"));
131 // XmlSerializableServices.AddDefaultSchema (schemas);
132 export.Statements.Add (new CodeMethodInvokeExpression (xmlSerializableServices, "AddDefaultSchema", new CodeArgumentReferenceExpression ("schemas")));
134 export.Statements.Add (new CodeMethodReturnStatement (new CodeFieldReferenceExpression (new CodeThisReferenceExpression (), "qname")));
136 td.Members.Add (export);
141 public bool CanImport (XmlSchemaSet schemas)
144 throw new ArgumentNullException ("schemas");
146 if (!schemas.IsCompiled)
149 foreach (XmlSchemaElement xe in schemas.GlobalElements.Values)
150 if (!CanImport (schemas, xe))
155 public bool CanImport (XmlSchemaSet schemas, ICollection<XmlQualifiedName> typeNames)
158 throw new ArgumentNullException ("schemas");
159 if (typeNames == null)
160 throw new ArgumentNullException ("typeNames");
162 if (!schemas.IsCompiled)
165 foreach (var name in typeNames)
166 if (!CanImport (schemas, name))
171 public bool CanImport (XmlSchemaSet schemas, XmlQualifiedName typeName)
174 throw new ArgumentNullException ("schemas");
175 if (typeName == null)
176 throw new ArgumentNullException ("typeName");
178 if (!schemas.IsCompiled)
181 if (IsPredefinedType (typeName))
182 return true; // while it just ignores...
184 if (!schemas.GlobalTypes.Contains (typeName))
187 return CanImport (schemas, schemas.GlobalTypes [typeName] as XmlSchemaType);
190 public bool CanImport (XmlSchemaSet schemas, XmlSchemaElement element)
193 throw new ArgumentNullException ("schemas");
195 throw new ArgumentNullException ("element");
197 if (!schemas.IsCompiled)
200 if (element.ElementSchemaType != null)
201 return CanImport (schemas, element.ElementSchemaType as XmlSchemaType);
202 else if (element.SchemaTypeName != null && !element.SchemaTypeName.Equals (QName.Empty))
203 return CanImport (schemas, element.SchemaTypeName);
211 bool CanImport (XmlSchemaSet schemas, XmlSchemaType type)
213 if (IsPredefinedType (type.QualifiedName))
216 var st = type as XmlSchemaSimpleType;
218 return CanImportSimpleType (schemas, st);
220 var ct = (XmlSchemaComplexType) type;
221 var sc = ct.ContentModel as XmlSchemaSimpleContent;
223 if (sc.Content is XmlSchemaSimpleContentExtension)
226 if (!CanImportComplexType (schemas, ct))
232 bool CanImportSimpleType (XmlSchemaSet schemas, XmlSchemaSimpleType type)
234 var scl = type.Content as XmlSchemaSimpleTypeList;
236 if (scl.ItemType == null)
238 var itemType = scl.ItemType as XmlSchemaSimpleType;
239 var ir = itemType.Content as XmlSchemaSimpleTypeRestriction;
242 return true; // as enum
244 var scr = type.Content as XmlSchemaSimpleTypeRestriction;
246 return true; // as enum
251 bool CanImportComplexType (XmlSchemaSet schemas, XmlSchemaComplexType type)
253 foreach (XmlSchemaAttribute att in type.AttributeUses.Values)
254 if (att.Use != XmlSchemaUse.Optional || att.QualifiedName.Namespace != KnownTypeCollection.MSSimpleNamespace)
257 CodeTypeReference baseClrType = null;
258 var particle = type.Particle;
259 if (type.ContentModel != null) {
260 var xsscr = type.ContentModel.Content as XmlSchemaSimpleContentRestriction;
262 if (xsscr.BaseType != null) {
263 if (!CanImport (schemas, xsscr.BaseType))
266 if (!CanImport (schemas, xsscr.BaseTypeName))
269 // The above will result in an error, but make sure to show we don't support it.
272 var xscce = type.ContentModel.Content as XmlSchemaComplexContentExtension;
274 if (!CanImport (schemas, xscce.BaseTypeName))
276 baseClrType = GetCodeTypeReferenceInternal (xscce.BaseTypeName, false);
278 var baseInfo = GetTypeInfo (xscce.BaseTypeName, false);
279 particle = xscce.Particle;
281 var xsccr = type.ContentModel.Content as XmlSchemaComplexContentRestriction;
286 var seq = particle as XmlSchemaSequence;
287 if (seq == null && particle != null)
292 if (seq.Items.Count == 1 && seq.Items [0] is XmlSchemaAny && type.Parent is XmlSchemaElement) {
294 // looks like it is not rejected (which contradicts the error message on .NET). See XsdDataContractImporterTest.ImportTestX32(). Also ImporTestX13() for Parent check.
298 foreach (var child in seq.Items)
299 if (!(child is XmlSchemaElement))
302 bool isDictionary = false;
303 if (type.Annotation != null) {
304 foreach (var ann in type.Annotation.Items) {
305 var ai = ann as XmlSchemaAppInfo;
306 if (ai != null && ai.Markup != null &&
307 ai.Markup.Length > 0 &&
308 ai.Markup [0].NodeType == XmlNodeType.Element &&
309 ai.Markup [0].LocalName == "IsDictionary" &&
310 ai.Markup [0].NamespaceURI == KnownTypeCollection.MSSimpleNamespace)
315 if (seq.Items.Count == 1) {
316 var pt = (XmlSchemaParticle) seq.Items [0];
317 var xe = pt as XmlSchemaElement;
318 if (pt.MaxOccursString == "unbounded") {
319 // import as a collection contract.
320 if (pt is XmlSchemaAny) {
321 } else if (isDictionary) {
322 var kvt = xe.ElementSchemaType as XmlSchemaComplexType;
323 var seq2 = kvt != null ? kvt.Particle as XmlSchemaSequence : null;
324 var k = seq2 != null && seq2.Items.Count == 2 ? seq2.Items [0] as XmlSchemaElement : null;
325 var v = seq2 != null && seq2.Items.Count == 2 ? seq2.Items [1] as XmlSchemaElement : null;
326 if (k == null || v == null)
328 if (!CanImport (schemas, k.ElementSchemaType))
330 if (!CanImport (schemas, v.ElementSchemaType))
334 } else if (type.QualifiedName.Namespace == KnownTypeCollection.MSArraysNamespace &&
335 IsPredefinedType (xe.ElementSchemaType.QualifiedName)) {
336 // then this CodeTypeDeclaration is to be removed, and CodeTypeReference to this type should be an array instead.
340 if (!CanImport (schemas, xe.ElementSchemaType))
348 // import as a (normal) contract.
349 var elems = new List<XmlSchemaElement> ();
350 foreach (XmlSchemaElement xe in seq.Items) {
351 if (xe.MaxOccurs != 1)
354 if (elems.Any (e => e.QualifiedName.Name == xe.QualifiedName.Name))
359 foreach (var xe in elems) {
360 // import property type in prior.
361 if (!CanImport (schemas, xe.ElementSchemaType.QualifiedName))
365 } // if (seq contains only an xs:any)
371 bool CanImport (XmlSchemaSet schemas, XmlSchemaComplexType type)
373 if (type == null || type.QualifiedName.Namespace == XmlSchema.Namespace) // xs:anyType -> not supported.
376 if (type.ContentModel is XmlSchemaSimpleContent) // simple content derivation is not supported.
378 if (type.ContentModel != null && type.ContentModel.Content != null) {
379 var xscce = type.ContentModel.Content as XmlSchemaComplexContentExtension;
380 if (xscce == null) // complex DBR is not supported.
383 if (xscce.BaseTypeName != qname_anytype && !CanImport (schemas, xscce.BaseTypeName))
393 public void Import (XmlSchemaSet schemas)
396 throw new ArgumentNullException ("schemas");
398 if (!schemas.IsCompiled)
401 foreach (XmlSchemaElement xe in schemas.GlobalElements.Values)
402 Import (schemas, xe);
405 public void Import (XmlSchemaSet schemas, ICollection<XmlQualifiedName> typeNames)
408 throw new ArgumentNullException ("schemas");
409 if (typeNames == null)
410 throw new ArgumentNullException ("typeNames");
411 foreach (var name in typeNames)
412 Import (schemas, name);
415 // This checks type existence and raises an error if it is missing.
416 public void Import (XmlSchemaSet schemas, XmlQualifiedName typeName)
419 throw new ArgumentNullException ("schemas");
420 if (typeName == null)
421 throw new ArgumentNullException ("typeName");
423 if (!schemas.IsCompiled)
426 if (IsPredefinedType (typeName))
429 if (!schemas.GlobalTypes.Contains (typeName))
430 throw new InvalidDataContractException (String.Format ("Type {0} is not found in the schemas", typeName));
432 Import (schemas, schemas.GlobalTypes [typeName] as XmlSchemaType, typeName);
435 public XmlQualifiedName Import (XmlSchemaSet schemas, XmlSchemaElement element)
438 throw new ArgumentNullException ("schemas");
440 throw new ArgumentNullException ("element");
442 var elname = element.QualifiedName;
444 if (IsPredefinedType (element.SchemaTypeName))
447 switch (elname.Namespace) {
448 case KnownTypeCollection.MSSimpleNamespace:
449 switch (elname.Name) {
458 if (!CanImport (schemas, element) && Options != null && Options.ImportXmlType) {
459 var qn = element.QualifiedName;
460 GenerateXmlType (qn);
464 if (element.ElementSchemaType != null) {
465 if (IsCollectionType (element.ElementSchemaType))
466 elname = element.ElementSchemaType.QualifiedName;
469 // FIXME: use element to fill nillable and arrays.
471 elname != null && !elname.Equals (QName.Empty) ? elname :
472 element.ElementSchemaType != null ? element.ElementSchemaType.QualifiedName :
475 if (element.ElementSchemaType != null)
476 Import (schemas, element.ElementSchemaType, qname);
477 else if (element.SchemaTypeName != null && !element.SchemaTypeName.Equals (QName.Empty))
478 Import (schemas, schemas.GlobalTypes [element.SchemaTypeName] as XmlSchemaType, qname);
479 // otherwise it is typeless == anyType.
481 Import (schemas, XmlSchemaType.GetBuiltInComplexType (qname_anytype), qname);
486 void Import (XmlSchemaSet schemas, XmlSchemaType type)
488 if (!CanImport (schemas, type) && Options != null && Options.ImportXmlType) {
489 GenerateXmlType (type.QualifiedName);
492 Import (schemas, type, type.QualifiedName);
495 void Import (XmlSchemaSet schemas, XmlSchemaType type, XmlQualifiedName qname)
497 var existing = imported_types.FirstOrDefault (it => it.XsdType == type);
498 if (existing != null)
499 return;// existing.XsdTypeName;
501 if (IsPredefinedType (type.QualifiedName))
504 DoImport (schemas, type, qname);
507 string GetUniqueName (string name, CodeNamespace cns)
510 if (!identifiers_table.TryGetValue (cns, out i)) {
511 i = new CodeIdentifiers ();
512 identifiers_table.Add (cns, i);
514 return i.AddUnique (name, null);
517 void DoImport (XmlSchemaSet schemas, XmlSchemaType type, XmlQualifiedName qname)
519 CodeNamespace cns = null;
520 CodeTypeReference clrRef;
521 cns = GetCodeNamespace (qname.Namespace);
522 clrRef = new CodeTypeReference (cns.Name.Length > 0 ? cns.Name + "." + qname.Name : qname.Name);
524 var td = new CodeTypeDeclaration () {
525 Name = GetUniqueName (CodeIdentifier.MakeValid (qname.Name), cns),
526 TypeAttributes = GenerateInternal ? TypeAttributes.NotPublic : TypeAttributes.Public,
530 var info = new TypeImportInfo () { ClrType = clrRef, XsdType = type, XsdTypeName = qname };
531 imported_types.Add (info);
533 var st = type as XmlSchemaSimpleType;
535 ImportSimpleType (td, schemas, st, qname);
537 var ct = (XmlSchemaComplexType) type;
538 var sc = ct.ContentModel as XmlSchemaSimpleContent;
540 if (sc.Content is XmlSchemaSimpleContentExtension)
541 throw new InvalidDataContractException (String.Format ("complex type '{0}' with simple content extension is not supported", type.QualifiedName));
543 if (!ImportComplexType (td, schemas, ct, qname)) {
544 cns.Types.Remove (td);
545 if (cns.Types.Count == 0)
546 CodeCompileUnit.Namespaces.Remove (cns);
549 foreach (var impinfo in imported_types)
550 for (; impinfo.KnownTypeOutputIndex < impinfo.KnownClrTypes.Count; impinfo.KnownTypeOutputIndex++)
551 td.CustomAttributes.Add (new CodeAttributeDeclaration (
552 new CodeTypeReference (typeof (KnownTypeAttribute)),
553 new CodeAttributeArgument (new CodeTypeOfExpression (impinfo.KnownClrTypes [impinfo.KnownTypeOutputIndex]))));
557 static readonly string ass_name = typeof (DataContractAttribute).Assembly.GetName ().Name;
558 static readonly string ass_version = typeof (DataContractAttribute).Assembly.GetName ().Version.ToString ();
559 static readonly CodeTypeReference typeref_data_contract = new CodeTypeReference (typeof (DataContractAttribute));
560 static readonly CodeTypeReference typeref_coll_contract = new CodeTypeReference (typeof (CollectionDataContractAttribute));
562 void AddTypeAttributes (CodeTypeDeclaration td, XmlSchemaType type, params XmlSchemaElement [] collectionArgs)
564 var name = type.QualifiedName;
565 // [GeneratedCode (assembly_name, assembly_version)]
566 td.CustomAttributes.Add (new CodeAttributeDeclaration (
567 new CodeTypeReference (typeof (GeneratedCodeAttribute)),
568 new CodeAttributeArgument (new CodePrimitiveExpression (ass_name)),
569 new CodeAttributeArgument (new CodePrimitiveExpression (ass_version))));
571 var ct = type as XmlSchemaComplexType;
573 // [DataContract(Name="foobar",Namespace="urn:foobar")] (optionally IsReference=true),
574 // or [CollectionDataContract(ditto, ItemType/KeyType/ValueType)]
575 var dca = new CodeAttributeDeclaration (
576 collectionArgs != null && collectionArgs.Length > 0 ? typeref_coll_contract : typeref_data_contract,
577 new CodeAttributeArgument ("Name", new CodePrimitiveExpression (name.Name)),
578 new CodeAttributeArgument ("Namespace", new CodePrimitiveExpression (name.Namespace)));
579 if (collectionArgs != null) {
580 if (collectionArgs.Length > 0)
581 dca.Arguments.Add (new CodeAttributeArgument ("ItemName", new CodePrimitiveExpression (CodeIdentifier.MakeValid (collectionArgs [0].QualifiedName.Name))));
582 if (collectionArgs.Length > 2) {
583 dca.Arguments.Add (new CodeAttributeArgument ("KeyName", new CodePrimitiveExpression (CodeIdentifier.MakeValid (collectionArgs [1].QualifiedName.Name))));
584 dca.Arguments.Add (new CodeAttributeArgument ("ValueName", new CodePrimitiveExpression (CodeIdentifier.MakeValid (collectionArgs [2].QualifiedName.Name))));
587 if (ct != null && ct.AttributeUses [new XmlQualifiedName ("Ref", KnownTypeCollection.MSSimpleNamespace)] != null)
588 dca.Arguments.Add (new CodeAttributeArgument ("IsReference", new CodePrimitiveExpression (true)));
589 td.CustomAttributes.Add (dca);
591 // optional [Serializable]
592 if (Options != null && Options.GenerateSerializable)
593 td.CustomAttributes.Add (new CodeAttributeDeclaration ("System.SerializableAttribute"));
596 static readonly CodeTypeReference typeref_ext_iface = new CodeTypeReference ("System.Runtime.Serialization.IExtensibleDataObject");
597 static readonly CodeTypeReference typeref_ext_class = new CodeTypeReference ("System.Runtime.Serialization.ExtensionDataObject");
599 void AddExtensionData (CodeTypeDeclaration td)
601 td.BaseTypes.Add (typeref_ext_iface);
603 var field = new CodeMemberField (typeref_ext_class, "extensionDataField");
604 td.Members.Add (field);
606 var prop = new CodeMemberProperty () { Type = field.Type, Name = "ExtensionData", Attributes = (GenerateInternal ? MemberAttributes.Assembly : MemberAttributes.Public) | MemberAttributes.Final };
607 prop.GetStatements.Add (new CodeMethodReturnStatement (
608 new CodeFieldReferenceExpression (
609 new CodeThisReferenceExpression (),
610 "extensionDataField")));
611 prop.SetStatements.Add (new CodeAssignStatement (
612 new CodeFieldReferenceExpression (
613 new CodeThisReferenceExpression (),
614 "extensionDataField"),
615 new CodePropertySetValueReferenceExpression ()));
617 td.Members.Add (prop);
620 void ImportSimpleType (CodeTypeDeclaration td, XmlSchemaSet schemas, XmlSchemaSimpleType type, XmlQualifiedName qname)
622 var scl = type.Content as XmlSchemaSimpleTypeList;
624 if (scl.ItemType == null)
625 throw new InvalidDataContractException (String.Format ("simple type list is allowed only with an anonymous simple type with enumeration restriction content as its item type definition (type is {0})", type.QualifiedName));
626 var itemType = scl.ItemType as XmlSchemaSimpleType;
627 var ir = itemType.Content as XmlSchemaSimpleTypeRestriction;
629 throw new InvalidDataContractException (String.Format ("simple type list is allowed only with an anonymous simple type with enumeration restriction content as its item type definition (type is {0})", type.QualifiedName));
630 ImportEnum (td, schemas, ir, type, qname, true);
633 var scr = type.Content as XmlSchemaSimpleTypeRestriction;
635 ImportEnum (td, schemas, scr, type, qname, false);
639 throw new InvalidDataContractException (String.Format ("simple type is supported only if it has enumeration or list of an anonymous simple type with enumeration restriction content as its item type definition (type is {0})", qname));
642 static readonly CodeTypeReference enum_member_att_ref = new CodeTypeReference (typeof (EnumMemberAttribute));
644 void ImportEnum (CodeTypeDeclaration td, XmlSchemaSet schemas, XmlSchemaSimpleTypeRestriction r, XmlSchemaType type, XmlQualifiedName qname, bool isFlag)
646 if (isFlag && !r.BaseTypeName.Equals (new XmlQualifiedName ("string", XmlSchema.Namespace)))
647 throw new InvalidDataContractException (String.Format ("For flags enumeration '{0}', the base type for the simple type restriction must be XML schema string", qname));
650 AddTypeAttributes (td, type);
652 td.CustomAttributes.Add (new CodeAttributeDeclaration (new CodeTypeReference (typeof (FlagsAttribute))));
654 foreach (var facet in r.Facets) {
655 var e = facet as XmlSchemaEnumerationFacet;
657 throw new InvalidDataContractException (String.Format ("Invalid simple type restriction (type {0}). Only enumeration is allowed.", qname));
658 var em = new CodeMemberField () { Name = CodeIdentifier.MakeValid (e.Value) };
659 var ea = new CodeAttributeDeclaration (enum_member_att_ref);
660 if (e.Value != em.Name)
661 ea.Arguments.Add (new CodeAttributeArgument ("Value", new CodePrimitiveExpression (e.Value)));
662 em.CustomAttributes.Add (ea);
667 // Returns false if it should remove the imported type.
668 bool IsCollectionType (XmlSchemaType type)
670 var complex = type as XmlSchemaComplexType;
674 var seq = complex.Particle as XmlSchemaSequence;
678 if (seq.Items.Count == 1 && seq.Items [0] is XmlSchemaAny && complex.Parent is XmlSchemaElement)
681 if (type.Annotation != null) {
682 foreach (var ann in type.Annotation.Items) {
683 var ai = ann as XmlSchemaAppInfo;
684 if (ai != null && ai.Markup != null &&
685 ai.Markup.Length > 0 &&
686 ai.Markup [0].NodeType == XmlNodeType.Element &&
687 ai.Markup [0].LocalName == "IsDictionary" &&
688 ai.Markup [0].NamespaceURI == KnownTypeCollection.MSSimpleNamespace)
693 if (seq.Items.Count != 1)
696 var pt = (XmlSchemaParticle) seq.Items [0];
697 var xe = pt as XmlSchemaElement;
698 if (pt.MaxOccursString != "unbounded")
701 return !(pt is XmlSchemaAny);
704 // Returns false if it should remove the imported type.
705 bool ImportComplexType (CodeTypeDeclaration td, XmlSchemaSet schemas, XmlSchemaComplexType type, XmlQualifiedName qname)
707 foreach (XmlSchemaAttribute att in type.AttributeUses.Values)
708 if (att.Use != XmlSchemaUse.Optional || att.QualifiedName.Namespace != KnownTypeCollection.MSSimpleNamespace)
709 throw new InvalidDataContractException (String.Format ("attribute in DataContract complex type '{0}' is limited to those in {1} namespace, and optional.", qname, KnownTypeCollection.MSSimpleNamespace));
711 CodeTypeReference baseClrType = null;
712 var particle = type.Particle;
713 if (type.ContentModel != null) {
714 var xsscr = type.ContentModel.Content as XmlSchemaSimpleContentRestriction;
716 if (xsscr.BaseType != null)
717 Import (schemas, xsscr.BaseType);
719 Import (schemas, xsscr.BaseTypeName);
720 // The above will result in an error, but make sure to show we don't support it.
721 throw new InvalidDataContractException (String.Format ("complex type simple content restriction is not supported in DataContract (type '{0}')", qname));
723 var xscce = type.ContentModel.Content as XmlSchemaComplexContentExtension;
725 Import (schemas, xscce.BaseTypeName);
726 baseClrType = GetCodeTypeReferenceInternal (xscce.BaseTypeName, false);
727 if (baseClrType != null)
728 td.BaseTypes.Add (baseClrType);
730 var baseInfo = GetTypeInfo (xscce.BaseTypeName, false);
731 if (baseInfo != null)
732 baseInfo.KnownClrTypes.Add (imported_types.First (it => it.XsdType == type).ClrType);
733 particle = xscce.Particle;
735 var xsccr = type.ContentModel.Content as XmlSchemaComplexContentRestriction;
737 throw new InvalidDataContractException (String.Format ("complex content type (for type '{0}') has a restriction content model, which is not supported in DataContract.", qname));
740 var seq = particle as XmlSchemaSequence;
741 if (seq == null && particle != null)
742 throw new InvalidDataContractException (String.Format ("Not supported particle {1}. In DataContract, only sequence particle is allowed as the top-level content of a complex type (type '{0}')", qname, particle));
746 if (seq.Items.Count == 1 && seq.Items [0] is XmlSchemaAny && type.Parent is XmlSchemaElement) {
748 // looks like it is not rejected (which contradicts the error message on .NET). See XsdDataContractImporterTest.ImportTestX32(). Also ImporTestX13() for Parent check.
752 foreach (var child in seq.Items)
753 if (!(child is XmlSchemaElement))
754 throw new InvalidDataContractException (String.Format ("Only local element is allowed as the content of the sequence of the top-level content of a complex type '{0}'. Other particles (sequence, choice, all, any, group ref) are not supported.", qname));
756 bool isDictionary = false;
757 if (type.Annotation != null) {
758 foreach (var ann in type.Annotation.Items) {
759 var ai = ann as XmlSchemaAppInfo;
760 if (ai != null && ai.Markup != null &&
761 ai.Markup.Length > 0 &&
762 ai.Markup [0].NodeType == XmlNodeType.Element &&
763 ai.Markup [0].LocalName == "IsDictionary" &&
764 ai.Markup [0].NamespaceURI == KnownTypeCollection.MSSimpleNamespace)
770 * Collection Type Support:
772 * We need to distinguish between normal array/dictionary collections and
773 * custom collection types which use [CollectionDataContract].
775 * The name of a normal collection type starts with "ArrayOf" and uses the
776 * element type's namespace. We use the collection type directly and don't
777 * generate a proxy class for these.
779 * The collection type (and the base class or a custom collection's proxy type)
780 * is dermined by 'ImportOptions.ReferencedCollectionTypes'. The default is to
781 * use an array for list collections and Dictionary<,> for dictionaries.
783 * Note that my implementation currently only checks for generic type definitions
784 * in the 'ImportOptions.ReferencedCollectionTypes' - it looks for something that
785 * implements IEnumerable<T> or IDictionary<K,V>. This is not complete, but it's
786 * all that's necessary to support different collection types in a GUI.
789 * var options = new ImportOptions ();
790 * options.ReferencedCollectionTypes.Add (typeof (LinkedList<>));
791 * options.ReferencedCollectionTypes.Add (typeof (SortedList<,>));
792 * to configure these; see XsdDataContractImportTest2.cs for some examples.
796 if (seq.Items.Count == 1) {
797 var pt = (XmlSchemaParticle) seq.Items [0];
798 var xe = pt as XmlSchemaElement;
799 if (pt.MaxOccursString == "unbounded") {
800 // import as a collection contract.
801 if (pt is XmlSchemaAny) {
802 } else if (isDictionary) {
803 var kvt = xe.ElementSchemaType as XmlSchemaComplexType;
804 var seq2 = kvt != null ? kvt.Particle as XmlSchemaSequence : null;
805 var k = seq2 != null && seq2.Items.Count == 2 ? seq2.Items [0] as XmlSchemaElement : null;
806 var v = seq2 != null && seq2.Items.Count == 2 ? seq2.Items [1] as XmlSchemaElement : null;
807 if (k == null || v == null)
808 throw new InvalidDataContractException (String.Format ("Invalid Dictionary contract type '{0}'. A Dictionary schema type must have a sequence particle which contains exactly two schema elements for key and value.", type.QualifiedName));
809 return ImportCollectionType (td, schemas, type, k, v);
811 return ImportCollectionType (td, schemas, type, xe);
815 throw new InvalidDataContractException (String.Format ("complex type '{0}' is an invalid Dictionary type definition. A Dictionary must have a sequence particle with exactly two child elements", qname));
817 // import as a (normal) contract.
818 var elems = new List<XmlSchemaElement> ();
819 foreach (XmlSchemaElement xe in seq.Items) {
820 if (xe.MaxOccurs != 1)
821 throw new InvalidDataContractException (String.Format ("schema complex type '{0}' has a content sequence containing an element '{1}' with 'maxOccurs' value as more than 1, which is not supported in DataContract.", qname, xe.QualifiedName));
823 if (elems.Any (e => e.QualifiedName.Name == xe.QualifiedName.Name))
824 throw new InvalidDataContractException (String.Format ("In schema type '{0}', there already is an element whose name is {1}, where duplicate of element names are not supported.", qname, xe.QualifiedName.Name));
828 foreach (var xe in elems) {
829 // import property type in prior.
830 Import (schemas, xe.ElementSchemaType.QualifiedName);
831 AddProperty (td, xe);
834 } // if (seq contains only an xs:any)
837 AddTypeAttributes (td, type);
838 AddExtensionData (td);
843 bool ImportCollectionType (CodeTypeDeclaration td, XmlSchemaSet schemas,
844 XmlSchemaComplexType type,
845 XmlSchemaElement key, XmlSchemaElement value)
847 Import (schemas, key.ElementSchemaType);
848 Import (schemas, value.ElementSchemaType);
849 var keyType = GetCodeTypeReference (key.ElementSchemaType.QualifiedName);
850 var valueType = GetCodeTypeReference (value.ElementSchemaType.QualifiedName);
852 var collectionType = GetDictionaryCollectionType ();
853 var baseTypeName = collectionType != null ?
854 collectionType.FullName : "System.Collections.Generic.Dictionary";
856 if (type.QualifiedName.Name.StartsWith ("ArrayOf")) {
857 // Standard collection, use the collection type instead of
858 // creating a proxy class.
859 var cti = imported_types.First (i => i.XsdType == type);
860 cti.ClrType = new CodeTypeReference (baseTypeName, keyType, valueType);
864 td.BaseTypes.Add (new CodeTypeReference (baseTypeName, keyType, valueType));
865 AddTypeAttributes (td, type, key);
866 AddTypeAttributes (td, type, value);
870 bool ImportCollectionType (CodeTypeDeclaration td, XmlSchemaSet schemas,
871 XmlSchemaComplexType type, XmlSchemaElement xe)
873 Import (schemas, xe.ElementSchemaType);
874 var element = GetCodeTypeReference (xe.ElementSchemaType.QualifiedName);
876 var collectionType = GetListCollectionType ();
878 if (type.QualifiedName.Name.StartsWith ("ArrayOf")) {
879 // Standard collection, use the collection type instead of
880 // creating a proxy class.
881 var cti = imported_types.First (i => i.XsdType == type);
882 if (collectionType != null)
883 cti.ClrType = new CodeTypeReference (collectionType.FullName, element);
885 cti.ClrType = new CodeTypeReference (element, 1);
889 var baseTypeName = collectionType != null ?
890 collectionType.FullName : "System.Collections.Generic.List";
892 td.BaseTypes.Add (new CodeTypeReference (baseTypeName, element));
893 AddTypeAttributes (td, type, xe);
897 bool ImplementsInterface (Type type, Type iface)
899 foreach (var i in type.GetInterfaces ()) {
900 if (i.Equals (iface))
902 if (i.IsGenericType && i.GetGenericTypeDefinition ().Equals (iface))
909 Type GetListCollectionType ()
911 if (import_options == null)
913 var listTypes = import_options.ReferencedCollectionTypes.Where (
914 t => t.IsGenericTypeDefinition && t.GetGenericArguments ().Length == 1 &&
915 ImplementsInterface (t, typeof (IEnumerable<>)));
916 return listTypes.FirstOrDefault ();
919 Type GetDictionaryCollectionType ()
921 if (import_options == null)
923 var dictTypes = import_options.ReferencedCollectionTypes.Where (
924 t => t.IsGenericTypeDefinition && t.GetGenericArguments ().Length == 2 &&
925 ImplementsInterface (t, typeof (IDictionary<,>)));
926 return dictTypes.FirstOrDefault ();
929 static readonly CodeExpression this_expr = new CodeThisReferenceExpression ();
930 static readonly CodeExpression arg_value_expr = new CodePropertySetValueReferenceExpression ();
932 bool GenerateInternal {
933 get { return Options != null && Options.GenerateInternal; }
936 void AddProperty (CodeTypeDeclaration td, XmlSchemaElement xe)
938 var att = GenerateInternal ? MemberAttributes.Assembly : MemberAttributes.Public;
939 var fi = new CodeMemberField () { Name = CodeIdentifier.MakeValid (xe.QualifiedName.Name + "Field"), Type = GetCodeTypeReference (xe.ElementSchemaType.QualifiedName, xe) };
941 var pi = new CodeMemberProperty () { Name = xe.QualifiedName.Name, Attributes = att, HasGet = true, HasSet = true, Type = fi.Type };
942 // [DataMember(Name=foobar, IsRequired=!nillable)]
943 var dma = new CodeAttributeDeclaration (
944 new CodeTypeReference (typeof (DataMemberAttribute)));
945 if (fi.Name != xe.QualifiedName.Name)
946 new CodeAttributeArgument ("Name", new CodePrimitiveExpression (xe.QualifiedName.Name));
948 new CodeAttributeArgument ("IsRequired", new CodePrimitiveExpression (true));
949 pi.CustomAttributes.Add (dma);
951 pi.GetStatements.Add (new CodeMethodReturnStatement () { Expression = new CodeFieldReferenceExpression (this_expr, fi.Name) });
952 pi.SetStatements.Add (new CodeAssignStatement (new CodeFieldReferenceExpression (this_expr, fi.Name), arg_value_expr));
958 bool IsPredefinedType (XmlQualifiedName qname)
962 switch (qname.Namespace) {
963 case KnownTypeCollection.MSSimpleNamespace:
964 return KnownTypeCollection.GetPrimitiveTypeFromName (qname) != null;
965 case XmlSchema.Namespace:
966 return XmlSchemaType.GetBuiltInSimpleType (qname) != null || XmlSchemaType.GetBuiltInComplexType (qname) != null;
971 CodeNamespace GetCodeNamespace (string xmlns)
974 if (Options == null || !Options.Namespaces.TryGetValue (xmlns, out ns))
975 ns = GetCodeNamespaceFromXmlns (xmlns);
977 foreach (CodeNamespace cns in CodeCompileUnit.Namespaces)
980 var newCns = new CodeNamespace () { Name = ns };
981 CodeCompileUnit.Namespaces.Add (newCns);
985 const string default_ns_prefix = "http://schemas.datacontract.org/2004/07/";
987 string GetCodeNamespaceFromXmlns (string xns)
989 if (xns.StartsWith (default_ns_prefix, StringComparison.Ordinal))
990 xns = xns.Substring (default_ns_prefix.Length);
994 if (Uri.TryCreate (xns, UriKind.Absolute, out u) && (tmp = MakeStringNamespaceComponentsValid (u.GetComponents (UriComponents.Host | UriComponents.Path, UriFormat.Unescaped))).Length > 0)
997 return MakeStringNamespaceComponentsValid (xns);
1000 static readonly char [] split_tokens = new char [] {'/', '.'};
1002 string MakeStringNamespaceComponentsValid (string ns)
1004 var arr = ns.Split (split_tokens, StringSplitOptions.RemoveEmptyEntries);
1005 for (int i = 0; i < arr.Length; i++)
1006 arr [i] = CodeIdentifier.MakeValid (arr [i]);
1007 return String.Join (".", arr);
1010 // Post-compilation information retrieval
1012 TypeImportInfo GetTypeInfo (XmlQualifiedName typeName, bool throwError)
1014 var info = imported_types.FirstOrDefault (
1015 i => i.XsdTypeName.Equals (typeName) || i.XsdType.QualifiedName.Equals (typeName));
1018 throw new InvalidOperationException (String.Format ("schema type '{0}' has not been imported yet. Import it first.", typeName));
1024 public CodeTypeReference GetCodeTypeReference (XmlQualifiedName typeName)
1026 return GetCodeTypeReferenceInternal (typeName, true);
1029 CodeTypeReference GetCodeTypeReferenceInternal (XmlQualifiedName typeName, bool throwError)
1031 if (typeName == null)
1032 throw new ArgumentNullException ("typeName");
1034 switch (typeName.Namespace) {
1035 case XmlSchema.Namespace:
1036 case KnownTypeCollection.MSSimpleNamespace:
1037 var pt = KnownTypeCollection.GetPrimitiveTypeFromName (typeName);
1039 throw new ArgumentException (String.Format ("Invalid type name in a predefined namespace: {0}", typeName));
1040 return new CodeTypeReference (pt);
1043 var info = GetTypeInfo (typeName, throwError);
1044 return info != null ? info.ClrType : null;
1047 [MonoTODO ("use element argument and fill Nullable etc.")]
1048 public CodeTypeReference GetCodeTypeReference (XmlQualifiedName typeName, XmlSchemaElement element)
1050 if (typeName == null)
1051 throw new ArgumentNullException ("typeName");
1052 if (element == null)
1053 throw new ArgumentNullException ("element");
1055 return GetCodeTypeReference (typeName);
1058 public ICollection<CodeTypeReference> GetKnownTypeReferences (XmlQualifiedName typeName)
1060 if (typeName == null)
1061 throw new ArgumentNullException ("typeName");
1063 return GetTypeInfo (typeName, true).KnownClrTypes;
1066 List<TypeImportInfo> imported_types = new List<TypeImportInfo> ();
1068 class TypeImportInfo
1070 public TypeImportInfo ()
1072 KnownClrTypes = new List<CodeTypeReference> ();
1075 public CodeTypeReference ClrType { get; set; }
1076 public XmlQualifiedName XsdTypeName { get; set; }
1077 public XmlSchemaType XsdType { get; set; }
1078 public List<CodeTypeReference> KnownClrTypes { get; private set; }
1079 public int KnownTypeOutputIndex { get; set; } // updated while importing.