///
/// MonoXSD.cs -- A reflection-based tool for dealing with XML Schema.
///
/// Author: Duncan Mak (duncan@ximian.com)
///
/// Copyright (C) 2003, Duncan Mak,
/// Ximian, Inc.
///
using System;
using System.Collections;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Text;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;
namespace Mono.Util {
public class Driver {
static void Main (string [] args) {
string assembly = args [0];
if (assembly.EndsWith (".dll") || assembly.EndsWith (".exe")) {
MonoXSD xsd = new MonoXSD ();
try {
xsd.WriteSchema (assembly);
} catch (ArgumentException e) {
Console.WriteLine (e.Message + "\n");
Environment.Exit (0);
}
} else {
Console.WriteLine ("Not supported.");
return;
}
}
}
public class MonoXSD {
Hashtable attributes;
int fileCount = 0;
bool isText = false;
BindingFlags flags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly;
XmlSchema schema = new XmlSchema ();
readonly string xs = "http://www.w3.org/2001/XMLSchema";
Hashtable generatedSchemaTypes;
public int FileCount {
set { fileCount = value; }
}
///
/// Writes a schema for each type in the assembly
///
public void WriteSchema (string assembly) {
Assembly a;
try {
a = Assembly.LoadFrom (assembly);
} catch (Exception e) {
Console.WriteLine ("Cannot use {0}, {1}", assembly, e.Message);
return;
}
generatedSchemaTypes = new Hashtable ();
XmlSchemaType schemaType;
foreach (Type t in a.GetTypes ()) {
try {
schemaType = WriteSchemaType (t);
} catch (ArgumentException e) {
throw new ArgumentException (String.Format ("Error: We cannot process {0}\n{1}", assembly, e.Message));
}
if (schemaType == null)
continue; // skip
XmlSchemaElement schemaElement = WriteSchemaElement (t, schemaType);
schema.Items.Add (schemaElement);
schema.Items.Add (schemaType);
}
schema.ElementFormDefault = XmlSchemaForm.Qualified;
schema.Compile (new ValidationEventHandler (OnSchemaValidation));
string output = String.Format ("schema{0}.xsd", fileCount);
XmlTextWriter writer = new XmlTextWriter (output, Encoding.UTF8);
writer.Formatting = Formatting.Indented;
schema.Write (writer);
Console.WriteLine ("Writing {0}.", output);
}
///
/// Given a Type and its associated schema type, add aa '' node
/// to the schema.
///
public XmlSchemaElement WriteSchemaElement (Type type, XmlSchemaType schemaType)
{
XmlSchemaElement schemaElement = new XmlSchemaElement ();
schemaElement.Name = type.Name;
if (schemaType.QualifiedName == null || schemaType.QualifiedName == XmlQualifiedName.Empty)
schemaElement.SchemaTypeName = new XmlQualifiedName (schemaType.Name);
else
schemaElement.SchemaTypeName = schemaType.QualifiedName;
if (schemaType is XmlSchemaComplexType)
schemaElement.IsNillable = true;
return schemaElement;
}
public void OnSchemaValidation (object sender, ValidationEventArgs args)
{
Console.WriteLine (args.Message);
}
///
/// From a Type, create a corresponding ComplexType node to
/// represent this Type.
///
public XmlSchemaType WriteSchemaType (Type type)
{
if (generatedSchemaTypes.Contains (type.FullName)) // Caching
return generatedSchemaTypes [type.FullName] as XmlSchemaType;
XmlSchemaType schemaType = new XmlSchemaType ();
attributes = new Hashtable ();
if (!type.IsAbstract && typeof (System.Delegate).IsAssignableFrom (type))
return null;
if (type.IsEnum)
return WriteEnumType (type);
else if (type != typeof (object) && type.BaseType != typeof (object)) {
try {
schemaType = WriteComplexSchemaType (type);
} catch (ArgumentException e) {
throw e;
}
} else {
XmlSchemaComplexType complexType = new XmlSchemaComplexType ();
complexType.Name = type.Name;
FieldInfo [] fields = type.GetFields (flags);
PropertyInfo [] properties = type.GetProperties (flags);
XmlSchemaSequence sequence;
try {
sequence = PopulateSequence (fields, properties);
if (attributes != null) {
foreach (object o in attributes.Keys) {
MemberInfo member = o as MemberInfo;
Type attribute_type = attributes [o] as Type;
if (attribute_type == typeof (System.Xml.Schema.XmlSchemaAnyAttribute))
complexType.AnyAttribute = new XmlSchemaAnyAttribute ();
else
complexType.Attributes.Add (WriteSchemaAttribute (member, attribute_type));
}
}
} catch (ArgumentException e) {
throw new ArgumentException (String.Format ("There is an error in '{0}'\n\t{1}", type.Name, e.Message));
}
if (isText) {
complexType.IsMixed = true;
isText = false;
}
complexType.Particle = sequence;
generatedSchemaTypes.Add (type.FullName, complexType);
schemaType = complexType;
}
return schemaType;
}
public XmlSchemaAttribute WriteSchemaAttribute (MemberInfo member, Type attribute_type)
{
if (member == null || attribute_type == null)
return null;
XmlSchemaAttribute attribute = new XmlSchemaAttribute ();
attribute.Name = member.Name;
attribute.SchemaTypeName = GetQualifiedName (attribute_type.FullName);
object [] attrs = member.GetCustomAttributes (false);
return attribute;
}
public XmlSchemaType WriteEnumType (Type type)
{
if (type.IsEnum == false)
throw new Exception (String.Format ("{0} is not an enumeration.", type.Name));
if (generatedSchemaTypes.Contains (type.FullName)) // Caching
return null;
XmlSchemaSimpleType simpleType = new XmlSchemaSimpleType ();
simpleType.Name = type.Name;
FieldInfo [] fields = type.GetFields ();
XmlSchemaSimpleTypeRestriction simpleRestriction = new XmlSchemaSimpleTypeRestriction ();
simpleType.Content = simpleRestriction;
simpleRestriction.BaseTypeName = new XmlQualifiedName ("string", xs);
foreach (FieldInfo field in fields) {
if (field.IsSpecialName)
continue;
XmlSchemaEnumerationFacet e = new XmlSchemaEnumerationFacet ();
e.Value = field.Name;
simpleRestriction.Facets.Add (e);
}
generatedSchemaTypes.Add (type.FullName, simpleType);
return simpleType;
}
public XmlSchemaType WriteArrayType (Type type, MemberInfo member)
{
if (generatedSchemaTypes.Contains (type.FullName)) // Caching
return null;
XmlSchemaComplexType complexType = new XmlSchemaComplexType ();
XmlQualifiedName qname = GetQualifiedName (type);
if (qname == null)
complexType.Name = type.Name;
else
complexType.Name = qname.Name;
XmlSchemaSequence sequence = new XmlSchemaSequence ();
XmlSchemaElement element = new XmlSchemaElement ();
element.MinOccurs = 0;
element.MaxOccursString = "unbounded";
element.IsNillable = true;
element.Name = qname.Name.ToLower ();
object [] attrs = member.GetCustomAttributes (false);
if (attrs.Length > 0) {
foreach (object o in attrs) {
if (o is XmlArrayItemAttribute) {
if (type.IsArray == false)
throw new ArgumentException (
String.Format ("XmlArrayAttribute is not applicable to {0}, because it is not an array.",
member.Name));
XmlArrayItemAttribute attr = (XmlArrayItemAttribute) o;
if (attr.ElementName.Length != 0)
element.Name = attr.ElementName;
continue;
}
if (o is XmlAnyElementAttribute)
return null;
}
}
element.SchemaTypeName = GetQualifiedName (
type.FullName.Substring (0, type.FullName.Length - 2));
sequence.Items.Add (element);
complexType.Particle = sequence;
generatedSchemaTypes.Add (type.FullName, complexType);
return complexType;
}
public XmlSchemaType WriteComplexSchemaType ()
{
XmlSchemaComplexType complexType = new XmlSchemaComplexType ();
XmlSchemaSequence sequence;
complexType.IsMixed = true;
sequence = new XmlSchemaSequence ();
sequence.Items.Add (new XmlSchemaAny ());
complexType.Particle = sequence;
return complexType;
}
///
/// Handle derivation by extension.
/// If type is null, it'll create a new complexType
/// with an XmlAny node in its sequence child node.
///
public XmlSchemaType WriteComplexSchemaType (Type type)
{
//
// Recursively generate schema for all parent types
//
if (type != null && type.BaseType == typeof (object))
return WriteSchemaType (type);
XmlSchemaComplexType complexType = new XmlSchemaComplexType ();
XmlSchemaSequence sequence;
XmlSchemaComplexContentExtension extension = new XmlSchemaComplexContentExtension ();
XmlSchemaComplexContent content = new XmlSchemaComplexContent ();
complexType.ContentModel = content;
content.Content = extension;
XmlSchemaType baseSchemaType = WriteSchemaType (type.BaseType);
complexType.Name = type.Name;
FieldInfo [] fields = type.GetFields (flags);
PropertyInfo [] properties = type.GetProperties (flags);
try {
sequence = PopulateSequence (fields, properties);
if (attributes != null) {
foreach (object o in attributes) {
MemberInfo member = (MemberInfo) o;
Type attribute_type = (Type) attributes [o];
complexType.Attributes.Add (WriteSchemaAttribute (member, attribute_type));
}
}
} catch (ArgumentException e) {
throw new ArgumentException (String.Format ("There is an error in '{0}'\n\t{1}", type.Name, e.Message));
}
extension.BaseTypeName = new XmlQualifiedName (baseSchemaType.Name);
extension.Particle = sequence;
generatedSchemaTypes.Add (type.FullName, complexType);
return complexType;
}
public XmlSchemaSequence PopulateSequence (FieldInfo [] fields, PropertyInfo [] properties)
{
if (fields.Length == 0 && properties.Length == 0)
return null;
XmlSchemaSequence sequence = new XmlSchemaSequence ();
try {
foreach (FieldInfo field in fields)
if (IsXmlAttribute (field))
attributes.Add (field, field.FieldType);
else if (IsXmlAnyAttribute (field))
attributes.Add (field,
typeof (System.Xml.Schema.XmlSchemaAnyAttribute));
else
AddElement (sequence, field, field.FieldType);
} catch (Exception e) {
throw e;
}
if (properties.Length == 0)
return sequence;
try {
foreach (PropertyInfo property in properties)
if (IsXmlAttribute (property))
attributes.Add (property, property.PropertyType);
else if (IsXmlAnyAttribute (property))
attributes.Add (property,
typeof (System.Xml.Schema.XmlSchemaAnyAttribute));
else {
AddElement (sequence, property, property.PropertyType);
}
} catch (ArgumentException e) {
throw e;
}
return sequence;
}
public bool IsXmlAttribute (MemberInfo member)
{
object [] attrs = member.GetCustomAttributes (
typeof (System.Xml.Serialization.XmlAttributeAttribute), false);
if (attrs.Length == 0)
return false;
else
return true;
}
public bool IsXmlAnyAttribute (MemberInfo member) {
object [] attrs = member.GetCustomAttributes (
typeof (System.Xml.Serialization.XmlAnyAttributeAttribute), false);
if (attrs.Length == 0)
return false;
else
return true;
}
///
/// Populates element nodes inside a '' node.
///
public void AddElement (XmlSchemaSequence sequence, MemberInfo member, Type type)
{
//
// Only read/write properties are supported.
//
if (member is PropertyInfo) {
PropertyInfo p = (PropertyInfo) member;
if ((p.CanRead && p.CanWrite) == false)
return;
}
//
// readonly fields are not supported.
//
if (member is FieldInfo) {
FieldInfo f = (FieldInfo) member;
if (f.IsInitOnly || f.IsLiteral)
return;
}
//
// delegates are not supported.
//
if (!type.IsAbstract && typeof (System.Delegate).IsAssignableFrom (type))
return;
//
// If it's an array, write a SchemaType for the type of array
//
if (type.IsArray) {
XmlSchemaType arrayType = WriteArrayType (type, member);
if (arrayType != null)
schema.Items.Add (arrayType);
}
XmlSchemaElement element = new XmlSchemaElement ();
element.Name = member.Name;
XmlQualifiedName schema_type_name = GetQualifiedName (type);
if (type.IsEnum) {
element.SchemaTypeName = new XmlQualifiedName (type.Name);
} else if (schema_type_name.Name == "xml") {
element.SchemaType = WriteComplexSchemaType ();
element.SchemaTypeName = XmlQualifiedName.Empty; // 'xml' is just a temporary name
} else if (schema_type_name == null) {
throw new ArgumentException (String.Format ("The type '{0}' cannot be represented in XML Schema.", type.FullName));
} else // this is the normal case
element.SchemaTypeName = schema_type_name;
object [] attrs = member.GetCustomAttributes (false);
if (attrs.Length > 0) {
foreach (object o in attrs) {
if (o is XmlElementAttribute) {
XmlElementAttribute attr = (XmlElementAttribute) o;
if (attr.DataType != null && attr.DataType.Length != 0)
element.SchemaTypeName = new XmlQualifiedName (attr.DataType, xs);
if (attr.ElementName != null && attr.ElementName.Length != 0)
element.Name = attr.ElementName;
continue;
}
if (o is XmlArrayAttribute) {
if (type.IsArray == false)
throw new ArgumentException (
String.Format ("XmlArrayAttribute is not applicable to {0}, because it is not an array.",
member.Name));
XmlArrayAttribute attr = (XmlArrayAttribute) o;
if (attr.ElementName.Length != 0)
element.Name = attr.ElementName;
continue;
}
//
// isText signals that the mixed="true" in the schema type.
//
if (o is XmlTextAttribute) {
isText = true;
return;
}
if (o is XmlAnyElementAttribute) {
XmlSchemaAny any = new XmlSchemaAny ();
any.MinOccurs = 0;
any.MaxOccursString = "unbounded";
sequence.Items.Add (any);
return;
}
}
}
if (type.IsClass)
element.MinOccurs = 0;
else if (type.IsValueType)
element.MinOccurs = 1;
element.MaxOccurs = 1;
sequence.Items.Add (element);
}
public XmlQualifiedName GetQualifiedName (Type type)
{
//
// XmlAttributes are not saved.
//
if (type.Equals (typeof (System.Xml.XmlAttribute)))
return null;
//
// Other derivatives of XmlNode are saved specially,
// as indicated by this "xml" flag.
//
if (type.Equals (typeof (System.Xml.XmlNode))
|| type.IsSubclassOf (typeof (System.Xml.XmlNode)))
return new XmlQualifiedName ("xml");
if (type.IsArray) {
TextInfo ti = CultureInfo.CurrentCulture.TextInfo;
string type_name = type.FullName.Substring (0, type.FullName.Length - 2);
Console.WriteLine ("type: " + type_name);
XmlQualifiedName qname = GetQualifiedName (type_name);
string array_type;
Console.WriteLine (type.Name);
if (qname != null)
array_type = ti.ToTitleCase (qname.Name);
else
array_type = ti.ToTitleCase (type.Name.Substring (0, type.Name.Length - 2));
return new XmlQualifiedName ("ArrayOf" + array_type);
}
return GetQualifiedName (type.FullName);
}
public XmlQualifiedName GetQualifiedName (string type)
{
string type_name;
switch (type) {
case "System.Uri":
type_name = "anyURI";
break;
case "System.Boolean":
type_name = "Boolean";
break;
case "System.SByte":
type_name = "Byte";
break;
case "System.DateTime":
type_name = "dateTime";
break;
case "System.Decimal":
type_name = "decimal";
break;
case "System.Double":
type_name = "Double";
break;
case "System.Int16":
type_name = "short";
break;
case "System.Int32":
type_name = "int";
break;
case "System.Int64":
type_name = "long";
break;
case "System.Xml.XmlQualifiedName":
type_name = "QName";
break;
case "System.TimeSpan":
type_name = "duration";
break;
case "System.String":
type_name = "string";
break;
case "System.UInt16":
type_name = "unsignedShort";
break;
case "System.UInt32":
type_name = "unsignedInt";
break;
case "System.UInt64":
type_name = "unsignedLong";
break;
default:
type_name = null;
break;
}
if (type_name == null)
return null;
else {
XmlQualifiedName name = new XmlQualifiedName (type_name, xs);
return name;
}
}
}
}