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