update MEF to preview 9
[mcs.git] / tools / mono-xsd / MonoXSD.cs
blob25af641f29ade4bc6bafae124a919e58a67acd18
1 ///
2 /// MonoXSD.cs -- A reflection-based tool for dealing with XML Schema.
3 ///
4 /// Author: Duncan Mak (duncan@ximian.com)
5 ///
6 /// Copyright (C) 2003, Duncan Mak,
7 /// Ximian, Inc.
8 ///
10 using System;
11 using System.Collections;
12 using System.Globalization;
13 using System.IO;
14 using System.Reflection;
15 using System.Text;
16 using System.Xml;
17 using System.Xml.Schema;
18 using System.Xml.Serialization;
20 namespace Mono.Util {
22 public class Driver {
24 public static readonly string helpString =
25 "MonoXSD.exe - a utility for generating schema or class files\n\nMonoXSD.exe <assembly>.dll|<assembly>.exe [/output:] [/type]\n";
27 static void Main (string [] args) {
29 if (args.Length < 1) {
30 Console.WriteLine (helpString);
31 Environment.Exit (0);
34 string input = args [0];
35 string lookup_type = null;
36 string output_dir = null;
38 if (input.EndsWith (".dll") || input.EndsWith (".exe")) {
40 if (args.Length >= 2 && args [1].StartsWith ("/o"))
41 output_dir = args [1].Substring (args [1].IndexOf (':') + 1);
43 if (args.Length >= 3 && args [2].StartsWith ("/t"))
44 lookup_type = args [2].Substring (args [2].IndexOf (':') + 1);
46 MonoXSD xsd = new MonoXSD ();
48 try {
49 xsd.WriteSchema (input, lookup_type, output_dir);
50 } catch (ArgumentException e) {
51 Console.WriteLine (e.Message + "\n");
52 Environment.Exit (0);
54 } else {
55 Console.WriteLine ("Not supported.");
56 return;
62 public class MonoXSD {
63 Hashtable attributes;
64 int fileCount = 0;
65 bool isText = false;
66 BindingFlags flags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly;
67 XmlSchema schema = new XmlSchema ();
68 readonly string xs = "http://www.w3.org/2001/XMLSchema";
69 Hashtable generatedSchemaTypes;
71 public int FileCount {
72 set { fileCount = value; }
75 /// <summary>
76 /// Writes a schema for each type in the assembly
77 /// </summary>
78 public void WriteSchema (string assembly, string lookup_type, string output_dir) {
80 Assembly a;
82 try {
83 a = Assembly.LoadFrom (assembly);
84 } catch (Exception e) {
85 Console.WriteLine ("Cannot use {0}, {1}", assembly, e.Message);
86 return;
89 generatedSchemaTypes = new Hashtable ();
91 XmlSchemaType schemaType;
93 foreach (Type t in a.GetTypes ()) {
95 if (lookup_type != null && t.Name != lookup_type)
96 continue;
98 try {
99 schemaType = WriteSchemaType (t);
100 } catch (ArgumentException e) {
101 throw new ArgumentException (String.Format ("Error: We cannot process {0}\n{1}", assembly, e.Message));
104 if (schemaType == null)
105 continue; // skip
107 XmlSchemaElement schemaElement = WriteSchemaElement (t, schemaType);
108 schema.Items.Add (schemaElement);
109 schema.Items.Add (schemaType);
112 schema.ElementFormDefault = XmlSchemaForm.Qualified;
113 schema.Compile (new ValidationEventHandler (OnSchemaValidation));
115 string output = String.Format ("schema{0}.xsd", fileCount);
117 if (output_dir != null)
118 output = Path.Combine (output_dir, output);
120 XmlTextWriter writer = new XmlTextWriter (output, Encoding.UTF8);
121 writer.Formatting = Formatting.Indented;
122 schema.Write (writer);
123 Console.WriteLine ("\nWriting {0}.", output);
126 /// <summary>
127 /// Given a Type and its associated schema type, add aa '<xs;element>' node
128 /// to the schema.
129 /// </summary>
130 public XmlSchemaElement WriteSchemaElement (Type type, XmlSchemaType schemaType)
132 XmlSchemaElement schemaElement = new XmlSchemaElement ();
133 schemaElement.Name = type.Name;
135 if (schemaType.QualifiedName == null)
136 schemaElement.SchemaTypeName = new XmlQualifiedName (schemaType.Name);
138 else
139 schemaElement.SchemaTypeName = schemaType.QualifiedName;
141 if (schemaType is XmlSchemaComplexType)
142 schemaElement.IsNillable = true;
144 return schemaElement;
147 public void OnSchemaValidation (object sender, ValidationEventArgs args)
149 Console.WriteLine (args.Message);
153 /// <summary>
154 /// From a Type, create a corresponding ComplexType node to
155 /// represent this Type.
156 /// </summary>
157 public XmlSchemaType WriteSchemaType (Type type)
159 if (generatedSchemaTypes.Contains (type.FullName)) // Caching
160 return generatedSchemaTypes [type.FullName] as XmlSchemaType;
162 XmlSchemaType schemaType = new XmlSchemaType ();
164 attributes = new Hashtable ();
166 if (!type.IsAbstract && typeof (System.Delegate).IsAssignableFrom (type))
167 return null;
169 if (type.IsEnum)
170 return WriteEnumType (type);
172 else if (type != typeof (object) && type.BaseType != typeof (object)) {
173 try {
174 schemaType = WriteComplexSchemaType (type);
175 } catch (ArgumentException e) {
176 throw e;
179 } else {
181 XmlSchemaComplexType complexType = new XmlSchemaComplexType ();
182 complexType.Name = type.Name;
183 FieldInfo [] fields = type.GetFields (flags);
184 PropertyInfo [] properties = type.GetProperties (flags);
185 XmlSchemaSequence sequence;
187 try {
188 sequence = PopulateSequence (fields, properties);
190 if (attributes != null) {
191 foreach (object o in attributes.Keys) {
192 MemberInfo member = o as MemberInfo;
193 Type attribute_type = attributes [o] as Type;
195 if (attribute_type == typeof (System.Xml.Schema.XmlSchemaAnyAttribute))
196 complexType.AnyAttribute = new XmlSchemaAnyAttribute ();
197 else
198 complexType.Attributes.Add (WriteSchemaAttribute (member, attribute_type));
202 } catch (ArgumentException e) {
203 throw new ArgumentException (String.Format ("There is an error in '{0}'\n\t{1}", type.Name, e.Message));
206 if (isText) {
207 complexType.IsMixed = true;
208 isText = false;
211 complexType.Particle = sequence;
212 generatedSchemaTypes.Add (type.FullName, complexType);
214 schemaType = complexType;
217 return schemaType;
220 public XmlSchemaAttribute WriteSchemaAttribute (MemberInfo member, Type attribute_type)
222 if (member == null || attribute_type == null)
223 return null;
225 XmlSchemaAttribute attribute = new XmlSchemaAttribute ();
226 attribute.Name = member.Name;
227 attribute.SchemaTypeName = GetQualifiedName (attribute_type.FullName);
229 object [] attrs = member.GetCustomAttributes (false);
231 return attribute;
234 public XmlSchemaType WriteEnumType (Type type)
236 if (type.IsEnum == false)
237 throw new Exception (String.Format ("{0} is not an enumeration.", type.Name));
239 if (generatedSchemaTypes.Contains (type.FullName)) // Caching
240 return null;
242 XmlSchemaSimpleType simpleType = new XmlSchemaSimpleType ();
243 simpleType.Name = type.Name;
244 FieldInfo [] fields = type.GetFields ();
246 XmlSchemaSimpleTypeRestriction simpleRestriction = new XmlSchemaSimpleTypeRestriction ();
247 simpleType.Content = simpleRestriction;
248 simpleRestriction.BaseTypeName = new XmlQualifiedName ("string", xs);
250 foreach (FieldInfo field in fields) {
251 if (field.IsSpecialName)
252 continue;
254 XmlSchemaEnumerationFacet e = new XmlSchemaEnumerationFacet ();
255 e.Value = field.Name;
256 simpleRestriction.Facets.Add (e);
259 generatedSchemaTypes.Add (type.FullName, simpleType);
260 return simpleType;
263 public XmlSchemaType WriteArrayType (Type type, MemberInfo member)
265 if (generatedSchemaTypes.Contains (type.FullName)) // Caching
266 return null;
268 XmlSchemaComplexType complexType = new XmlSchemaComplexType ();
270 XmlQualifiedName qname = GetQualifiedName (type);
272 if (qname == null)
273 complexType.Name = type.Name;
274 else
275 complexType.Name = qname.Name;
277 XmlSchemaSequence sequence = new XmlSchemaSequence ();
278 XmlSchemaElement element = new XmlSchemaElement ();
280 element.MinOccurs = 0;
281 element.MaxOccursString = "unbounded";
282 element.IsNillable = true;
283 element.Name = qname.Name.ToLower ();
285 object [] attrs = member.GetCustomAttributes (false);
287 if (attrs.Length > 0) {
288 foreach (object o in attrs) {
289 if (o is XmlArrayItemAttribute) {
290 if (type.IsArray == false)
291 throw new ArgumentException (
292 String.Format ("XmlArrayAttribute is not applicable to {0}, because it is not an array.",
293 member.Name));
295 XmlArrayItemAttribute attr = (XmlArrayItemAttribute) o;
297 if (attr.ElementName.Length != 0)
298 element.Name = attr.ElementName;
300 continue;
303 if (o is XmlAnyElementAttribute)
304 return null;
308 element.SchemaTypeName = GetQualifiedName (
309 type.FullName.Substring (0, type.FullName.Length - 2));
311 sequence.Items.Add (element);
312 complexType.Particle = sequence;
314 generatedSchemaTypes.Add (type.FullName, complexType);
315 return complexType;
318 public XmlSchemaType WriteComplexSchemaType ()
320 XmlSchemaComplexType complexType = new XmlSchemaComplexType ();
321 XmlSchemaSequence sequence;
322 complexType.IsMixed = true;
323 sequence = new XmlSchemaSequence ();
324 sequence.Items.Add (new XmlSchemaAny ());
325 complexType.Particle = sequence;
326 return complexType;
330 /// <summary>
331 /// Handle derivation by extension.
332 /// If type is null, it'll create a new complexType
333 /// with an XmlAny node in its sequence child node.
334 /// </summary>
335 public XmlSchemaType WriteComplexSchemaType (Type type)
338 // Recursively generate schema for all parent types
340 if (type != null && type.BaseType == typeof (object))
341 return WriteSchemaType (type);
343 XmlSchemaComplexType complexType = new XmlSchemaComplexType ();
344 XmlSchemaSequence sequence;
345 XmlSchemaComplexContentExtension extension = new XmlSchemaComplexContentExtension ();
346 XmlSchemaComplexContent content = new XmlSchemaComplexContent ();
348 complexType.ContentModel = content;
349 content.Content = extension;
351 XmlSchemaType baseSchemaType = WriteSchemaType (type.BaseType);
353 complexType.Name = type.Name;
355 FieldInfo [] fields = type.GetFields (flags);
356 PropertyInfo [] properties = type.GetProperties (flags);
358 try {
359 sequence = PopulateSequence (fields, properties);
360 if (attributes != null) {
361 foreach (object o in attributes) {
362 MemberInfo member = (MemberInfo) o;
363 Type attribute_type = (Type) attributes [o];
365 complexType.Attributes.Add (WriteSchemaAttribute (member, attribute_type));
368 } catch (ArgumentException e) {
369 throw new ArgumentException (String.Format ("There is an error in '{0}'\n\t{1}", type.Name, e.Message));
372 extension.BaseTypeName = new XmlQualifiedName (baseSchemaType.Name);
373 extension.Particle = sequence;
375 generatedSchemaTypes.Add (type.FullName, complexType);
376 return complexType;
379 public XmlSchemaSequence PopulateSequence (FieldInfo [] fields, PropertyInfo [] properties)
381 if (fields.Length == 0 && properties.Length == 0)
382 return null;
384 XmlSchemaSequence sequence = new XmlSchemaSequence ();
386 try {
387 foreach (FieldInfo field in fields)
388 if (IsXmlAttribute (field))
389 attributes.Add (field, field.FieldType);
390 else if (IsXmlAnyAttribute (field))
391 attributes.Add (field,
392 typeof (System.Xml.Schema.XmlSchemaAnyAttribute));
393 else
394 AddElement (sequence, field, field.FieldType);
396 } catch (Exception e) {
397 throw e;
400 if (properties.Length == 0)
401 return sequence;
402 try {
403 foreach (PropertyInfo property in properties)
404 if (IsXmlAttribute (property))
405 attributes.Add (property, property.PropertyType);
406 else if (IsXmlAnyAttribute (property))
407 attributes.Add (property,
408 typeof (System.Xml.Schema.XmlSchemaAnyAttribute));
409 else {
410 AddElement (sequence, property, property.PropertyType);
413 } catch (ArgumentException e) {
414 throw e;
417 return sequence;
420 public bool IsXmlAttribute (MemberInfo member)
422 object [] attrs = member.GetCustomAttributes (
423 typeof (System.Xml.Serialization.XmlAttributeAttribute), false);
425 if (attrs.Length == 0)
426 return false;
427 else
428 return true;
431 public bool IsXmlAnyAttribute (MemberInfo member) {
432 object [] attrs = member.GetCustomAttributes (
433 typeof (System.Xml.Serialization.XmlAnyAttributeAttribute), false);
435 if (attrs.Length == 0)
436 return false;
437 else
438 return true;
441 ///<summary>
442 /// Populates element nodes inside a '<xs:sequence>' node.
443 ///</summary>
444 public void AddElement (XmlSchemaSequence sequence, MemberInfo member, Type type)
447 // Only read/write properties are supported.
449 if (member is PropertyInfo) {
450 PropertyInfo p = (PropertyInfo) member;
451 if ((p.CanRead && p.CanWrite) == false)
452 return;
456 // readonly fields are not supported.
458 if (member is FieldInfo) {
459 FieldInfo f = (FieldInfo) member;
460 if (f.IsInitOnly || f.IsLiteral)
461 return;
465 // delegates are not supported.
467 if (!type.IsAbstract && typeof (System.Delegate).IsAssignableFrom (type))
468 return;
471 // If it's an array, write a SchemaType for the type of array
473 if (type.IsArray) {
474 XmlSchemaType arrayType = WriteArrayType (type, member);
475 if (arrayType != null)
476 schema.Items.Add (arrayType);
479 XmlSchemaElement element = new XmlSchemaElement ();
481 element.Name = member.Name;
482 XmlQualifiedName schema_type_name = GetQualifiedName (type);
484 if (type.IsEnum) {
485 element.SchemaTypeName = new XmlQualifiedName (type.Name);
487 } else if (schema_type_name.Name == "xml") {
488 element.SchemaType = WriteComplexSchemaType ();
489 element.SchemaTypeName = XmlQualifiedName.Empty; // 'xml' is just a temporary name
491 } else if (schema_type_name == null) {
492 throw new ArgumentException (String.Format ("The type '{0}' cannot be represented in XML Schema.", type.FullName));
494 } else // this is the normal case
495 element.SchemaTypeName = schema_type_name;
497 object [] attrs = member.GetCustomAttributes (false);
499 if (attrs.Length > 0) {
500 foreach (object o in attrs) {
501 if (o is XmlElementAttribute) {
502 XmlElementAttribute attr = (XmlElementAttribute) o;
504 if (attr.DataType != null && attr.DataType.Length != 0)
505 element.SchemaTypeName = new XmlQualifiedName (attr.DataType, xs);
506 if (attr.ElementName != null && attr.ElementName.Length != 0)
507 element.Name = attr.ElementName;
509 continue;
512 if (o is XmlArrayAttribute) {
514 if (type.IsArray == false)
515 throw new ArgumentException (
516 String.Format ("XmlArrayAttribute is not applicable to {0}, because it is not an array.",
517 member.Name));
519 XmlArrayAttribute attr = (XmlArrayAttribute) o;
521 if (attr.ElementName.Length != 0)
522 element.Name = attr.ElementName;
524 continue;
528 // isText signals that the mixed="true" in the schema type.
530 if (o is XmlTextAttribute) {
531 isText = true;
532 return;
535 if (o is XmlAnyElementAttribute) {
536 XmlSchemaAny any = new XmlSchemaAny ();
537 any.MinOccurs = 0;
538 any.MaxOccursString = "unbounded";
539 sequence.Items.Add (any);
540 return;
545 if (type.IsClass)
546 element.MinOccurs = 0;
547 else if (type.IsValueType)
548 element.MinOccurs = 1;
550 element.MaxOccurs = 1;
552 sequence.Items.Add (element);
555 public XmlQualifiedName GetQualifiedName (Type type)
558 // XmlAttributes are not saved.
560 if (type.Equals (typeof (System.Xml.XmlAttribute)))
561 return null;
564 // Other derivatives of XmlNode are saved specially,
565 // as indicated by this "xml" flag.
567 if (type.Equals (typeof (System.Xml.XmlNode))
568 || type.IsSubclassOf (typeof (System.Xml.XmlNode)))
569 return new XmlQualifiedName ("xml");
571 if (type.IsArray) {
572 TextInfo ti = CultureInfo.CurrentCulture.TextInfo;
573 string type_name = type.FullName.Substring (0, type.FullName.Length - 2);
575 XmlQualifiedName qname = GetQualifiedName (type_name);
576 string array_type;
578 if (qname != null)
579 array_type = ti.ToTitleCase (qname.Name);
580 else
581 array_type = ti.ToTitleCase (type.Name.Substring (0, type.Name.Length - 2));
583 return new XmlQualifiedName ("ArrayOf" + array_type);
586 return GetQualifiedName (type.FullName);
589 public XmlQualifiedName GetQualifiedName (string type)
591 string type_name;
593 switch (type) {
594 case "System.Uri":
595 type_name = "anyURI";
596 break;
597 case "System.Boolean":
598 type_name = "Boolean";
599 break;
600 case "System.SByte":
601 type_name = "Byte";
602 break;
603 case "System.DateTime":
604 type_name = "dateTime";
605 break;
606 case "System.Decimal":
607 type_name = "decimal";
608 break;
609 case "System.Double":
610 type_name = "Double";
611 break;
612 case "System.Int16":
613 type_name = "short";
614 break;
615 case "System.Int32":
616 type_name = "int";
617 break;
618 case "System.Int64":
619 type_name = "long";
620 break;
621 case "System.Xml.XmlQualifiedName":
622 type_name = "QName";
623 break;
624 case "System.TimeSpan":
625 type_name = "duration";
626 break;
627 case "System.String":
628 type_name = "string";
629 break;
630 case "System.UInt16":
631 type_name = "unsignedShort";
632 break;
633 case "System.UInt32":
634 type_name = "unsignedInt";
635 break;
636 case "System.UInt64":
637 type_name = "unsignedLong";
638 break;
639 default:
640 type_name = null;
641 break;
644 if (type_name == null)
645 return null;
647 else {
648 XmlQualifiedName name = new XmlQualifiedName (type_name, xs);
649 return name;