2005-01-31 Zoltan Varga <vargaz@freemail.hu>
[mono.git] / mcs / tools / mono-xsd / XSD2Class.cs
1 //
2 // XSD2Class - xml schema based class generator
3 //
4 // Author
5 //      Atsushi Enomoto (ginga@kit.hi-ho.ne.jp)
6 //
7 // (C)2003 Atsushi Enomoto
8 //
9 // TODO:
10 //      * Currently it is developed with MS.NET and Microsoft.CSharp namespace.
11 //      * Handling members of choice fields are on changing. No enum types are
12 //        generated and/or collected and registered as fields of the enum.
13 //      * maxOccurs should be considered (and should emit member as array, if
14 //        required).
15 //
16 //      * It is desirable to have an alternative generator that generates
17 //        property members instead of simple fields, which checks their values 
18 //        in relation to their facets. (It may contradict XmlTypeMapping way,
19 //        which seems not to have xml schema type itself, so we (they?) cannot 
20 //        get any facets from typemapping).
21 //
22
23 using System;
24 using System.CodeDom;
25 using System.Collections;
26 using System.Text;
27 using System.Xml;
28 using System.Xml.Schema;
29 using System.Xml.Serialization;
30 using Microsoft.CSharp;
31
32 namespace Commons.Xml.XSD2ClassLib
33 {
34         /*
35         public class Driver
36         {
37                 public static void Main (string [] args)
38                 {
39                         if (args.Length < 1) {
40                                 Console.WriteLine ("usage: xsd2class [filename]");
41                                 return;
42                         }
43                         XmlTextReader xtr = new XmlTextReader (args [0]);
44 //                      xtr.XmlResolver = null;
45                         ValidationEventHandler errorHandler = new ValidationEventHandler (OnValidationError);
46                         XmlSchema xs = XmlSchema.Read (xtr, errorHandler);
47                         xs.Compile (errorHandler);
48                         XmlSchemas schemas = new XmlSchemas ();
49                         schemas.Add (xs);
50                         new XSD2Class ().Generate (schemas);
51                 }
52
53                 static void OnValidationError (object o, ValidationEventArgs e)
54                 {
55                         // Hey... is it sane doing !?
56                         Console.WriteLine (e.Exception.ToString ());
57                 }
58
59         }
60         */
61
62         public class XSD2Class
63         {
64                 XmlSchemas schemas;
65                 CodeCompileUnit codeCompileUnit;
66                 CodeNamespace codeNamespace;
67                 CodeTypeDeclaration currentType;
68                 Hashtable codeTypes = new Hashtable ();
69
70                 // Constructor
71
72                 public XSD2Class ()
73                 {
74                 }
75
76                 // Main process
77
78                 public void Generate (XmlSchemas schemas)
79                 {
80                         Generate (schemas, new CodeNamespace ());
81                 }
82
83                 public void Generate (XmlSchemas schemas, CodeNamespace codeNamespace)
84                 {
85                         Generate (schemas, codeNamespace, new CodeCompileUnit ());
86                 }
87
88                 public void Generate (XmlSchemas schemas,
89                         CodeNamespace codeNamespace,
90                         CodeCompileUnit codeCompileUnit)
91                 {
92                         this.schemas = schemas;
93                         this.codeCompileUnit = codeCompileUnit;
94                         this.codeNamespace = codeNamespace;
95                         codeCompileUnit.Namespaces.Add (codeNamespace);
96
97                         foreach (XmlSchema schema in schemas)
98                                 GenerateSchemaTypes (schema);
99
100                         new CSharpCodeProvider ().CreateGenerator ().GenerateCodeFromCompileUnit (codeCompileUnit, Console.Out, null);
101                 }
102
103                 public void GenerateSchemaTypes (XmlSchema schema)
104                 {
105                         foreach (XmlSchemaObject sob in schema.Items) {
106                                 XmlSchemaElement element = sob as XmlSchemaElement;
107                                 if (element == null)
108                                         continue;
109                                 XmlSchemaComplexType xsType = element.ElementType as XmlSchemaComplexType;
110                                 if (xsType == null)
111                                         continue;
112
113                                 GenerateComplexType (element.QualifiedName.Name, xsType);
114                         }
115                 }
116
117                 // Type generation
118
119                 private void GenerateComplexType (XmlSchemaComplexType xsType)
120                 {
121                         GenerateComplexType ("", xsType);
122                 }
123
124                 private void GenerateComplexType (string elementName, XmlSchemaComplexType xsType)
125                 {
126                         string typeName = xsType.QualifiedName.Name;
127                         if (typeName == "")
128                                 typeName = elementName;
129                         if (codeTypes.Contains (typeName))
130                                 return;
131
132                         currentType = CreateType (typeName);
133                         codeTypes.Add (xsType.QualifiedName.Name, currentType);
134                         codeNamespace.Types.Add (currentType);
135                         // base type
136                         XmlSchemaComplexType baseComplexType = xsType.BaseSchemaType as XmlSchemaComplexType;
137                         if (baseComplexType != null) {
138                                 GenerateComplexType (baseComplexType);
139 //                              currentType.BaseTypes = new CodeTypeReferenceCollection ();
140                                 currentType.BaseTypes.Add (new CodeTypeReference (((CodeTypeDeclaration) codeTypes [baseComplexType.QualifiedName.Name]).Name));
141                         } else if (xsType.BaseSchemaType != null) {
142                                 // TODO: insufficient. e.g. XmlQualifiedName
143                                 currentType.BaseTypes.Add (new CodeTypeReference (((XmlSchemaSimpleType) xsType.BaseSchemaType).Name));
144                         }
145
146                         // anyAttribute
147                         if (xsType.AnyAttribute != null)
148                                 currentType.Members.Add (CreateMemberField (
149                                         typeof (XmlAttribute).FullName, "AnyAttr", XmlStructureType.AnyAttribute));
150
151                         // attributes
152                         foreach (XmlSchemaAttribute schemaAtt in xsType.Attributes)
153                                 GenerateAttributeField (schemaAtt);
154
155                         // elements
156                         if (xsType.Particle != null) {
157                                 // particle
158                                 GenerateParticleField (xsType.Particle);
159                         } else if (xsType.ContentModel != null) {
160                                 XmlSchemaComplexContentExtension ce = xsType.ContentModel.Content as XmlSchemaComplexContentExtension;
161                                 XmlSchemaComplexContentRestriction cr = xsType.ContentModel.Content as XmlSchemaComplexContentRestriction;
162                                 if (ce != null)
163                                         GenerateParticleField (ce.Particle);
164                                 else if (cr != null)
165                                         GenerateParticleField (cr.Particle);
166                                 // TODO: handle simpleContent (how to?)
167                         }
168                 }
169
170                 // Field generation
171
172                 private void GenerateAttributeField (XmlSchemaAttribute schemaAtt)
173                 {
174                         XmlSchemaDatatype primitive = schemaAtt.AttributeType 
175                                 as XmlSchemaDatatype;
176                         XmlSchemaSimpleType simple = schemaAtt.AttributeType 
177                                 as XmlSchemaSimpleType;
178                         XmlSchemaDerivationMethod deriv =
179                                 XmlSchemaDerivationMethod.None;
180
181                         while (primitive == null) {
182                                 if (simple == null)     // maybe union
183                                         break;
184                                 primitive = simple.BaseSchemaType 
185                                         as XmlSchemaDatatype;
186                                 if (primitive == null) {
187                                         simple = simple.BaseSchemaType 
188                                                 as XmlSchemaSimpleType;
189                                         if (simple != null && simple.DerivedBy != XmlSchemaDerivationMethod.None)
190                                                 deriv = simple.DerivedBy;
191                                 }
192                         }
193
194                         Type type = primitive != null ?
195                                 primitive.ValueType : typeof (object);
196                         bool isList = (simple != null && simple.DerivedBy == XmlSchemaDerivationMethod.List);
197                         CodeTypeReference cType = new CodeTypeReference (type);
198                         cType.ArrayRank = isList ? 1 : 0;
199
200                         CodeMemberField cmf = CreateMemberField (cType, schemaAtt.QualifiedName.Name, XmlStructureType.Attribute);
201                         currentType.Members.Add (cmf);
202                 }
203
204                 private void GenerateElementField (XmlSchemaElement schemaElem)
205                 {
206                         CodeMemberField cmf;
207
208                         XmlSchemaDatatype dt = 
209                                 schemaElem.ElementType as XmlSchemaDatatype;
210                         XmlSchemaSimpleType st = 
211                                 schemaElem.ElementType as XmlSchemaSimpleType;
212                         // TODO: see GenerateAttributeField to know how to get correct type.
213                         if (st != null)
214                                 dt = st.Datatype;
215                         bool isList = (st != null && st.DerivedBy == XmlSchemaDerivationMethod.List);
216
217                         if (schemaElem.ElementType == null) {
218                                 CodeTypeReference cType = new CodeTypeReference (typeof (object));
219                                 cType.ArrayRank = isList ? 1 : 0;
220                                 cmf = CreateMemberField (cType,
221                                         schemaElem.QualifiedName.Name);
222                                 currentType.Members.Add (cmf);
223                         } else if (dt != null) {
224                                 // simple type member.
225                                 CodeTypeReference cType = new CodeTypeReference (dt.ValueType);
226                                 cType.ArrayRank = isList ? 1 : 0;
227                                 cmf = CreateMemberField (cType,
228                                         schemaElem.QualifiedName.Name);
229                                 currentType.Members.Add (cmf);
230                         } else {
231                                 // complex type member.
232                                 XmlSchemaComplexType ct = schemaElem.ElementType
233                                         as XmlSchemaComplexType;
234
235                                 CodeTypeDeclaration ctd = currentType;
236                                 GenerateComplexType (ct);
237                                 currentType = ctd;
238                                 CodeTypeDeclaration cType = codeTypes [ct.QualifiedName.Name] as CodeTypeDeclaration;
239                                 cmf = CreateMemberField (cType.Name, schemaElem.QualifiedName.Name);
240                                 currentType.Members.Add (cmf);
241                         }
242                 }
243
244                 private void GenerateParticleField (XmlSchemaParticle particle)
245                 {
246                         if (particle is XmlSchemaAny)
247                                 GenerateParticleAnyField (particle 
248                                         as XmlSchemaAny);
249                         else if (particle is XmlSchemaElement)
250                                 GenerateElementField (particle 
251                                         as XmlSchemaElement);
252                         else if (particle is XmlSchemaAll)
253                                 GenerateParticleAllField (particle 
254                                         as XmlSchemaAll);
255                         else if (particle is XmlSchemaChoice)
256                                 GenerateParticleChoiceField (particle 
257                                         as XmlSchemaChoice);
258                         else if (particle is XmlSchemaSequence)
259                                 GenerateParticleSequenceField (particle 
260                                         as XmlSchemaSequence);
261                         else if (particle is XmlSchemaGroupRef) {
262                                 XmlSchemaGroupRef gRef = particle 
263                                         as XmlSchemaGroupRef;
264                                 GenerateGroupField (FindGroup (gRef.RefName));
265                         }
266                 }
267
268                 private void GenerateParticleAnyField (XmlSchemaAny xsany)
269                 {
270                         CodeMemberField cmf = CreateMemberField (
271                                 typeof (XmlElement).FullName, "Any");
272                         currentType.Members.Add (cmf);
273                 }
274
275                 private void GenerateParticleAllField (XmlSchemaAll xsall)
276                 {
277                         foreach (XmlSchemaParticle cp in xsall.Items)
278                                 GenerateParticleField (cp);
279                 }
280
281                 private void GenerateParticleSequenceField (XmlSchemaSequence sequence)
282                 {
283                         foreach (XmlSchemaParticle cp in sequence.Items)
284                                 GenerateParticleField (cp);
285                 }
286
287                 private void GenerateParticleChoiceField (XmlSchemaChoice choice)
288                 {
289 #if true
290                         foreach (XmlSchemaParticle cp in choice.Items)
291                                 GenerateParticleField (cp);
292 #else
293                         // TODO: first, collect all choice alternatives that
294                         // they might be common typed elements. In such case,
295                         // no enum fields and types should be created.
296                         Type itemType = typeof (Object);
297
298                         // enum type generation
299                         // [XmlType (IncludeInSchema=false)]
300                         CodeTypeDeclaration enumType = CreateType ("ItemChoiceType", false);
301                         enumType.IsEnum = true;
302                         // TODO: add enum members.
303                         codeNamespace.Types.Add (enumType);
304
305                         // add enum field
306                         CodeMemberField cid = CreateMemberField (
307                                 enumType.Name, "ItemElementType");
308                         cid.CustomAttributes.Add (new CodeAttributeDeclaration (
309                                         typeof (XmlIgnoreAttribute).FullName));
310                         currentType.Members.Add (cid);
311
312                         // add item field
313                         // TODO: type should be computed whether common or not.
314                         CodeMemberField cmf = CreateMemberField (
315                                 itemType.FullName, "Item");
316                         CodeAttributeDeclaration choiceIdent =
317                                 new CodeAttributeDeclaration (
318                                 typeof (XmlChoiceIdentifierAttribute).FullName);
319                         choiceIdent.Arguments.Add (new CodeAttributeArgument (
320                                 "MemberName",
321                                 new CodePrimitiveExpression (cid.Name)));
322                         cmf.CustomAttributes.Add (choiceIdent);
323                         currentType.Members.Add (cmf);
324 #endif
325                 }
326
327                 private void GenerateGroupField (XmlSchemaGroup group)
328                 {
329                         GenerateParticleField (group.Particle);
330                 }
331
332                 // CreateMemberField
333
334                 private CodeMemberField CreateMemberField (string typeName, string name)
335                 {
336                         return CreateMemberField (typeName, name, XmlStructureType.Element);
337                 }
338
339                 private CodeMemberField CreateMemberField (string typeName, string name, XmlStructureType sType)
340                 {
341                         return CreateMemberField (new CodeTypeReference (typeName), name, sType);
342                 }
343
344                 private CodeMemberField CreateMemberField (CodeTypeReference reference, string name)
345                 {
346                         return CreateMemberField (reference, name, XmlStructureType.Element);
347                 }
348
349                 private CodeMemberField CreateMemberField (CodeTypeReference reference, string xmlName, XmlStructureType sType)
350                 {
351                         int i = 1;
352                         string clrName = xmlName;
353                         if (CodeMemberContains (clrName)) {
354                                 while (CodeMemberContains (clrName + i))
355                                         i++;
356                                 clrName = clrName + i;
357                         }
358
359                         CodeMemberField cmf = new CodeMemberField (reference, clrName);
360                         cmf.Attributes = MemberAttributes.Public;
361
362                         switch (sType) {
363                         case XmlStructureType.Element:
364                                 if (clrName != xmlName)
365                                         cmf.CustomAttributes.Add (CreateXmlAttribute (typeof (XmlElementAttribute), xmlName));
366                                 break;
367                         case XmlStructureType.Attribute:
368                                 cmf.CustomAttributes.Add (CreateXmlAttribute (typeof (XmlAttributeAttribute), clrName != xmlName ? xmlName : null));
369                                 break;
370                         case XmlStructureType.AnyAttribute:
371                                 cmf.CustomAttributes.Add (CreateXmlAttribute (typeof (XmlAnyAttributeAttribute), null));
372                                 reference.ArrayRank = 1;
373                                 break;
374                         }
375
376                         return cmf;
377                 }
378
379                 // CreateType
380
381                 private CodeTypeDeclaration CreateType (string xmlName)
382                 {
383                         return CreateType (xmlName, true);
384                 }
385
386                 private CodeTypeDeclaration CreateType (string xmlName, bool includeInSchema)
387                 {
388                         int i = 1;
389                         string clrName = CodeIdentifier.MakeValid (xmlName);
390                         if (CodeTypeContains (clrName)) {
391                                 while (CodeTypeContains (clrName + i))
392                                         i++;
393                                 clrName = clrName + i;
394                         }
395
396                         CodeTypeDeclaration decl = new CodeTypeDeclaration (clrName);
397                         if (includeInSchema) {
398                                 if (xmlName != clrName)
399                                         decl.CustomAttributes.Add (CreateXmlAttribute (typeof (XmlTypeAttribute), xmlName));
400                         } else {
401                                 CodeAttributeDeclaration xt = new CodeAttributeDeclaration (typeof (XmlTypeAttribute).FullName);
402                                 xt.Arguments.Add (new CodeAttributeArgument (
403                                         "IncludeInSchema",
404                                         new CodePrimitiveExpression (false)));
405                                 decl.CustomAttributes.Add (xt);
406                         }
407                         return decl;
408                 }
409
410                 // Utilities
411
412                 private XmlSchemaGroup FindGroup (XmlQualifiedName qname)
413                 {
414                         foreach (XmlSchema schema in schemas) {
415                                 foreach (XmlQualifiedName name in schema.Groups.Names) {
416                                         XmlSchemaGroup group = schema.Groups [name] as XmlSchemaGroup;
417                                         if (group.Name == qname.Name)
418                                                 return group;
419                                 }
420                         }
421                         return null;
422                 }
423
424                 private bool CodeTypeContains (string name)
425                 {
426                         for (int i=0; i<codeNamespace.Types.Count; i++)
427                                 if (codeNamespace.Types [i].Name == name)
428                                         return true;
429                         return false;
430                 }
431
432                 private bool CodeMemberContains (string name)
433                 {
434                         for (int i=0; i<currentType.Members.Count; i++)
435                                 if (currentType.Members [i].Name == name)
436                                         return true;
437                         return false;
438                 }
439
440                 private CodeAttributeDeclaration CreateXmlAttribute (Type attrType, string name)
441                 {
442                         CodeAttributeDeclaration xmlAtt = new CodeAttributeDeclaration (attrType.FullName);
443                         if (name != null)
444                                 xmlAtt.Arguments.Add (new CodeAttributeArgument ("Name", new CodePrimitiveExpression (name)));
445
446                         return xmlAtt;
447                 }
448
449 #if false
450                 // XmlSchemaImporter emulation
451
452                 public XmlTypeMapping ImportTypeMapping (XmlQualifiedName qname)
453                 {
454                         XmlSchemaComplexType xsType = FindComplexType (qname);
455                         if (xsType == null)
456                                 throw new InvalidOperationException ("Type " + qname + " not found.");
457
458                         GenerateComplexType (xsType);
459                         return null;
460                 }
461
462                 private XmlSchemaComplexType FindComplexType (XmlQualifiedName qname)
463                 {
464                         foreach (XmlSchema schema in schemas) {
465                                 foreach (XmlQualifiedName name in schema.SchemaTypes.Names) {
466                                         XmlSchemaType xsType = schema.SchemaTypes [name] as XmlSchemaType;
467                                         if (xsType is XmlSchemaSimpleType)
468                                                 continue;
469                                         if (xsType.QualifiedName == qname)
470                                                         return xsType as XmlSchemaComplexType;
471                                 }
472                         }
473                         return null;
474                 }
475 #endif
476         }
477
478         internal enum XmlStructureType
479         {
480                 Element,
481                 Attribute,
482                 AnyAttribute
483         }
484 }