2 /// MonoXSD.cs -- A reflection-based tool for dealing with XML Schema.
4 /// Author: Duncan Mak (duncan@ximian.com)
6 /// Copyright (C) 2003, Duncan Mak,
11 using System
.Collections
;
12 using System
.Globalization
;
14 using System
.Reflection
;
17 using System
.Xml
.Schema
;
18 using System
.Xml
.Serialization
;
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
);
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 ();
49 xsd
.WriteSchema (input
, lookup_type
, output_dir
);
50 } catch (ArgumentException e
) {
51 Console
.WriteLine (e
.Message
+ "\n");
55 Console
.WriteLine ("Not supported.");
62 public class MonoXSD
{
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; }
76 /// Writes a schema for each type in the assembly
78 public void WriteSchema (string assembly
, string lookup_type
, string output_dir
) {
83 a
= Assembly
.LoadFrom (assembly
);
84 } catch (Exception e
) {
85 Console
.WriteLine ("Cannot use {0}, {1}", assembly
, e
.Message
);
89 generatedSchemaTypes
= new Hashtable ();
91 XmlSchemaType schemaType
;
93 foreach (Type t
in a
.GetTypes ()) {
95 if (lookup_type
!= null && t
.Name
!= lookup_type
)
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)
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
);
127 /// Given a Type and its associated schema type, add aa '<xs;element>' node
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
);
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
);
154 /// From a Type, create a corresponding ComplexType node to
155 /// represent this Type.
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
))
170 return WriteEnumType (type
);
172 else if (type
!= typeof (object) && type
.BaseType
!= typeof (object)) {
174 schemaType
= WriteComplexSchemaType (type
);
175 } catch (ArgumentException e
) {
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
;
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 ();
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
));
207 complexType
.IsMixed
= true;
211 complexType
.Particle
= sequence
;
212 generatedSchemaTypes
.Add (type
.FullName
, complexType
);
214 schemaType
= complexType
;
220 public XmlSchemaAttribute
WriteSchemaAttribute (MemberInfo member
, Type attribute_type
)
222 if (member
== null || attribute_type
== null)
225 XmlSchemaAttribute attribute
= new XmlSchemaAttribute ();
226 attribute
.Name
= member
.Name
;
227 attribute
.SchemaTypeName
= GetQualifiedName (attribute_type
.FullName
);
229 object [] attrs
= member
.GetCustomAttributes (false);
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
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
)
254 XmlSchemaEnumerationFacet e
= new XmlSchemaEnumerationFacet ();
255 e
.Value
= field
.Name
;
256 simpleRestriction
.Facets
.Add (e
);
259 generatedSchemaTypes
.Add (type
.FullName
, simpleType
);
263 public XmlSchemaType
WriteArrayType (Type type
, MemberInfo member
)
265 if (generatedSchemaTypes
.Contains (type
.FullName
)) // Caching
268 XmlSchemaComplexType complexType
= new XmlSchemaComplexType ();
270 XmlQualifiedName qname
= GetQualifiedName (type
);
273 complexType
.Name
= type
.Name
;
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.",
295 XmlArrayItemAttribute attr
= (XmlArrayItemAttribute
) o
;
297 if (attr
.ElementName
.Length
!= 0)
298 element
.Name
= attr
.ElementName
;
303 if (o
is XmlAnyElementAttribute
)
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
);
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
;
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.
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
);
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
);
379 public XmlSchemaSequence
PopulateSequence (FieldInfo
[] fields
, PropertyInfo
[] properties
)
381 if (fields
.Length
== 0 && properties
.Length
== 0)
384 XmlSchemaSequence sequence
= new XmlSchemaSequence ();
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
));
394 AddElement (sequence
, field
, field
.FieldType
);
396 } catch (Exception e
) {
400 if (properties
.Length
== 0)
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
));
410 AddElement (sequence
, property
, property
.PropertyType
);
413 } catch (ArgumentException e
) {
420 public bool IsXmlAttribute (MemberInfo member
)
422 object [] attrs
= member
.GetCustomAttributes (
423 typeof (System
.Xml
.Serialization
.XmlAttributeAttribute
), false);
425 if (attrs
.Length
== 0)
431 public bool IsXmlAnyAttribute (MemberInfo member
) {
432 object [] attrs
= member
.GetCustomAttributes (
433 typeof (System
.Xml
.Serialization
.XmlAnyAttributeAttribute
), false);
435 if (attrs
.Length
== 0)
442 /// Populates element nodes inside a '<xs:sequence>' node.
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)
456 // readonly fields are not supported.
458 if (member
is FieldInfo
) {
459 FieldInfo f
= (FieldInfo
) member
;
460 if (f
.IsInitOnly
|| f
.IsLiteral
)
465 // delegates are not supported.
467 if (!type
.IsAbstract
&& typeof (System
.Delegate
).IsAssignableFrom (type
))
471 // If it's an array, write a SchemaType for the type of array
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
);
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
;
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.",
519 XmlArrayAttribute attr
= (XmlArrayAttribute
) o
;
521 if (attr
.ElementName
.Length
!= 0)
522 element
.Name
= attr
.ElementName
;
528 // isText signals that the mixed="true" in the schema type.
530 if (o
is XmlTextAttribute
) {
535 if (o
is XmlAnyElementAttribute
) {
536 XmlSchemaAny any
= new XmlSchemaAny ();
538 any
.MaxOccursString
= "unbounded";
539 sequence
.Items
.Add (any
);
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
)))
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");
572 TextInfo ti
= CultureInfo
.CurrentCulture
.TextInfo
;
573 string type_name
= type
.FullName
.Substring (0, type
.FullName
.Length
- 2);
575 XmlQualifiedName qname
= GetQualifiedName (type_name
);
579 array_type
= ti
.ToTitleCase (qname
.Name
);
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
)
595 type_name
= "anyURI";
597 case "System.Boolean":
598 type_name
= "Boolean";
603 case "System.DateTime":
604 type_name
= "dateTime";
606 case "System.Decimal":
607 type_name
= "decimal";
609 case "System.Double":
610 type_name
= "Double";
621 case "System.Xml.XmlQualifiedName":
624 case "System.TimeSpan":
625 type_name
= "duration";
627 case "System.String":
628 type_name
= "string";
630 case "System.UInt16":
631 type_name
= "unsignedShort";
633 case "System.UInt32":
634 type_name
= "unsignedInt";
636 case "System.UInt64":
637 type_name
= "unsignedLong";
644 if (type_name
== null)
648 XmlQualifiedName name
= new XmlQualifiedName (type_name
, xs
);