Click here to Skip to main content
15,895,370 members
Articles / Web Development / ASP.NET

XsdTidy beautifies the Xsd.exe output *with full DocBook .NET Wrapper*

Rate me:
Please Sign up or sign in to vote.
4.89/5 (32 votes)
1 Mar 20048 min read 186.3K   2.4K   72  
Refactors the Xsd.exe classes. Shipped with a full .NET wrapper of DocBook.
using System;
using System.Collections;
using System.Collections.Specialized;
using System.Threading;
using System.Reflection;
using System.Reflection.Emit;
using System.Xml.Serialization;

namespace XsdTidy
{
	using XsdTidy.Collections;

	/// <summary>
	/// Transforms the XSD wrapper classes outputed by the Xsd.exe utility
	/// to nicer classes using Reflection.
	/// </summary>
	/// <remarks>
	/// </remarks>
	public class XsdWrapperGenerator
	{
		private AssemblyBuilder assembly;
		private ModuleBuilder module;
		private NameConformer conformer;
		private string defaultNamespace;
		private TypeTypeBuilderDictionary builders = new TypeTypeBuilderDictionary();
		private FieldInfoFieldBuidlerDictionary fieldBuilders = new FieldInfoFieldBuidlerDictionary();
		private TypeBuilderConstructorBuilderDictionary contructorBuilders = new TypeBuilderConstructorBuilderDictionary();
		private bool alwaysCreateCollections = true;

		#region Public Interface

		/// <summary>
		/// Creates a new wrapper generator.
		/// </summary>
		/// <param name="name"></param>
		public XsdWrapperGenerator(string name, Version version)
		{
			if (name==null)
				throw new ArgumentNullException("mb");

			this.defaultNamespace = name;
			this.conformer = new NameConformer();

			BuildAssembly(name,version);
		}

		/// <summary>
		/// XsdWrapperGenerator
		/// </summary>
		/// <param name="name">assembly output name (without extension)</param>
		/// <param name="conformer">object that takes care of the camel
		/// capit consersion</param>
		public XsdWrapperGenerator(string name, Version version, NameConformer conformer)
		{
			if (name==null)
				throw new ArgumentNullException("mb");
			if (conformer==null)
				throw new ArgumentNullException("conformer");

			this.defaultNamespace = name;
			this.conformer = conformer;

			BuildAssembly(name,version);
		}

		private void BuildAssembly(string name, Version version)
		{

			AssemblyName assemblyName = new AssemblyName();
			assemblyName.Name = name;
			assemblyName.Version = version;
			this.assembly = Thread.GetDomain().DefineDynamicAssembly(
				assemblyName, 
				AssemblyBuilderAccess.Save
				);

			this.module = assembly.DefineDynamicModule(name + ".dll");
		}


		/// <summary>
		/// Adds a class to the wrapped class list
		/// </summary>
		/// <param name="t"></param>
		public void AddClass(Type t)
		{
			if (t==null)
				throw new ArgumentNullException("t");
			if (this.builders.Contains(t))
				return;

			// create type builder for classType
			TypeBuilder  typeBuilder = module.DefineType(
				this.defaultNamespace + "." + this.conformer.ToCapitalized(t.Name),
				TypeAttributes.Public | TypeAttributes.Class
				);
			// add builder to hashtable
			this.builders.Add(t,typeBuilder);
		}

		/// <summary>
		/// Emits wrappers for classes
		/// </summary>
		public void WrapClasses()
		{
			OnTypePass();
			foreach(DictionaryEntry de in this.builders)
			{
				WrapClass(
					(TypeBuilder)de.Value,
					(Type)de.Key
					);
			}

			OnConstructorDefinePass();
			// generate default constructors
			foreach(DictionaryEntry de in this.builders)
			{
				// add constructors
				GenerateDefaultConstructor(
					(TypeBuilder)de.Value,
					(Type)de.Key
					);
			}

			// generate properties
			OnPropertyPass();
			foreach(DictionaryEntry de in this.builders)
			{
				Type t = (Type)de.Key;
				foreach(FieldInfo f in t.GetFields())
				{
					GenerateProperty(
						(TypeBuilder)de.Value,
						f);
				}
			}

			// populate default constructors
			OnConstructorBuildPass();
			foreach(DictionaryEntry de in this.builders)
			{
				// add constructors
				PopulateDefaultConstructor(
					(TypeBuilder)de.Value,
					(Type)de.Key
					);
				// generate other constructors
				GenerateConstructors(
					(TypeBuilder)de.Value,
					(Type)de.Key
					);
			}
		}


		/// <summary>
		/// Saves the generated assembly to file
		/// </summary>
		public void Save()
		{
			foreach(TypeBuilder tb in this.builders.Values)
				tb.CreateType();
			this.assembly.Save(this.module.Name);

			this.builders.Clear();
			this.module = null;
			this.assembly = null;
		}

		/// <summary>
		/// Gets a value indicating if collection members need to be allocated
		/// </summary>
		public bool AlwaysCreateCollections
		{
			get
			{
				return this.alwaysCreateCollections;
			}
			set
			{
				this.alwaysCreateCollections = value;
			}
		}

		#endregion

		#region Events
		public event EventHandler TypePass;
		protected void OnTypePass()
		{
			if (this.TypePass!=null)
				TypePass(this,new EventArgs());
		}
		public event StringEventHandler TypeBuild;
		protected void OnTypeBuild(string msg)
		{
			if (this.TypeBuild!=null)
				TypeBuild(this,new StringEventArgs(msg));
		}
		public event StringEventHandler FieldBuild;
		protected void OnFieldBuild(string msg)
		{
			if (this.FieldBuild!=null)
				FieldBuild(this,new StringEventArgs(msg));
		}

		public event EventHandler PropertyPass;
		protected void OnPropertyPass()
		{
			if (this.PropertyPass!=null)
				PropertyPass(this,new EventArgs());
		}
		public event StringEventHandler PropertyBuild;
		protected void OnPropertyBuild(string msg)
		{
			if (this.PropertyBuild!=null)
				PropertyBuild(this,new StringEventArgs(msg));
		}

		public event EventHandler ConstructorDefinePass;
		protected void OnConstructorDefinePass()
		{
			if (this.ConstructorDefinePass!=null)
				ConstructorDefinePass(this,new EventArgs());
		}
		public event StringEventHandler ConstructorDefine;
		protected void OnConstructorDefine(string msg)
		{
			if (this.ConstructorDefine!=null)
				ConstructorDefine(this,new StringEventArgs(msg));
		}

		public event EventHandler ConstructorBuildPass;
		protected void OnConstructorBuildPass()
		{
			if (this.ConstructorBuildPass!=null)
				ConstructorBuildPass(this,new EventArgs());
		}
		public event StringEventHandler ConstructorBuild;
		protected void OnConstructorBuild(string msg)
		{
			if (this.ConstructorBuild!=null)
				ConstructorBuild(this,new StringEventArgs(msg));
		}
		#endregion

		#region Type Creation
		/// <summary>
		/// Creates a wrapper of type <paramref name="t"/> using
		/// <paramref name="tb"/>.
		/// </summary>
		/// <param name="t"></param>
		/// <param name="tb"></param>
		private void WrapClass(TypeBuilder tb, Type t)
		{
			OnTypeBuild(tb.Name);
			// set XmlRoot attribute
			ConstructorInfo ci = TypeHelper.GetConstructor(
				typeof(XmlRootAttribute),typeof(string));
			CustomAttributeBuilder xmlRoot = new CustomAttributeBuilder(
				ci,
				new Object[] {t.Name} 
				);
			tb.SetCustomAttribute(xmlRoot);

			// wrap fields of type
			foreach(FieldInfo field in t.GetFields())
			{
				WrapField(tb,field);
			}
		}
		#endregion

		#region Constructor Generation

		private void GenerateDefaultConstructor(TypeBuilder tb, Type t)
		{
			OnConstructorDefine(tb.Name);
			// get object constructor
			ConstructorBuilder ctor = tb.DefineConstructor(
				MethodAttributes.Public,
				CallingConventions.Standard,
				Type.EmptyTypes);

			this.contructorBuilders.Add(tb,ctor);
		}

		private void PopulateDefaultConstructor(TypeBuilder tb, Type t)
		{
			OnConstructorBuild(t.BaseType.Name);
			ConstructorInfo objCtor = TypeHelper.GetDefaultConstructor(typeof(Object));
			ConstructorBuilder ctor = this.contructorBuilders[tb];

			// emiting consturctor IL
			ILGenerator ctorIL = ctor.GetILGenerator();

			// call base constructor
			ctorIL.Emit(OpCodes.Ldarg_0);
			ctorIL.Emit(OpCodes.Call, objCtor);

			// emit other values
			EmitDefaultValues(ctorIL,t.GetFields());

			// finished
			ctorIL.Emit(OpCodes.Ret);
		}

		/// <summary>
		/// Generate constructor with requested values
		/// </summary>
		/// <param name="tb"></param>
		/// <param name="wrapped"></param>
		private void GenerateConstructors(TypeBuilder tb,Type t)
		{
			// get field types that cannot be null...
			ArrayList fields = new ArrayList();
			ArrayList defaultFields = new ArrayList();
			foreach(FieldInfo f in t.GetFields())
			{
				if (TypeHelper.IsXmlNullable(f) && !f.FieldType.IsArray)
					fields.Add(f);
				else
					defaultFields.Add(f);
			}
			if (fields.Count==0)
				return;

			Type[] ctorParams = new Type[fields.Count];
			for(int i = 0;i<fields.Count;++i)
			{
				FieldInfo fi = (FieldInfo)fields[i];
				if (this.builders.Contains(fi.FieldType))
					ctorParams[i] = this.builders[fi.FieldType];
				else if (this.fieldBuilders.Contains(fi))
					ctorParams[i] = this.fieldBuilders[fi].FieldType;
				else
					ctorParams[i] = fi.FieldType;
			}

			// get object constructor
			ConstructorInfo objCtor = TypeHelper.GetDefaultConstructor(typeof(Object));
			ConstructorBuilder ctor = tb.DefineConstructor(
				MethodAttributes.Public,
				CallingConventions.Standard,
				ctorParams);

			// emiting consturctor IL
			ILGenerator ctorIL = ctor.GetILGenerator();

			// call base constructor
			ctorIL.Emit(OpCodes.Ldarg_0);
			ctorIL.Emit(OpCodes.Call, objCtor);

			// assign arguments
			for(int i = 0;i<fields.Count;++i)
			{
				FieldInfo f = (FieldInfo)fields[i];
				// push wrapped on the stack
				ctorIL.Emit(OpCodes.Ldarg_0);
				switch(i)
				{
					case 0:
						ctorIL.Emit(OpCodes.Ldarg_1);break;
					case 1:
						ctorIL.Emit(OpCodes.Ldarg_2);break;
					case 2:
						ctorIL.Emit(OpCodes.Ldarg_3);break;
					default:
						// push index on stack
						ctorIL.Emit(OpCodes.Ldind_I2,i+1);
						// push ith variable on the stack
						ctorIL.Emit(OpCodes.Ldarg_S);
						break;
				}
				// assign field
				ctorIL.Emit(OpCodes.Stfld, f); 
			}

			// emit other values
			EmitDefaultValues(ctorIL,defaultFields);

			// finished
			ctorIL.Emit(OpCodes.Ret);
		}

	
		/// <summary>
		/// Emits default values of field
		/// </summary>
		/// <param name="il"></param>
		/// <param name="wrapped"></param>
		/// <param name="fields"></param>
		internal void EmitDefaultValues(
			ILGenerator il, 
			ICollection fields
			)
		{
			// wrapping fields
			int i = 0;
			foreach(FieldInfo f in fields)
			{
				++i;

				Type t = f.FieldType;
				if (this.builders.Contains(t))
					t = this.builders[t];
				else if (this.fieldBuilders.Contains(f))
					t = this.fieldBuilders[f].FieldType;

				if (f.FieldType.IsValueType)
					continue;
				if (TypeHelper.IsXmlNullable(f) 
					&& !(f.FieldType.IsArray && this.alwaysCreateCollections)
					)
					continue;
				if (t==typeof(Object))
					continue;

				il.Emit(OpCodes.Ldarg_0);

				// call default constructor
				// check if string
				CreateNewObject(il,t);
				il.Emit(OpCodes.Stfld, f);
			}
		}

		private ConstructorInfo GetDefaultConstructor(Type t)
		{
			if (t is TypeBuilder)
			{
				return this.contructorBuilders[(TypeBuilder)t];
			}
			else
				return TypeHelper.GetDefaultConstructor(t);	
		}

		private void CreateNewObject(ILGenerator il, Type t)
		{
			if (t==typeof(String))
			{
				il.Emit(OpCodes.Ldstr,"");
			}
			else
			{
				ConstructorInfo fCtr = GetDefaultConstructor(t);
				il.Emit(OpCodes.Newobj,fCtr);
			}
		}

		#endregion


		#region Field and property generation
		private void WrapField(TypeBuilder tb,FieldInfo field)
		{
			if (tb==null)
				throw new ArgumentNullException("tb");
			if (field==null)
				throw new ArgumentNullException("field");

			string camel = this.conformer.ToCamel(field.Name);
			OnFieldBuild(tb.Name+"."+camel);

			// add field
			Type wrappedField = null;
			if (this.builders.Contains(field.FieldType))
				wrappedField=this.builders[field.FieldType];
			else
				wrappedField=field.FieldType;

			// check if type is an array, otherwize replace by collaction
			if (wrappedField==typeof(string[]))
				wrappedField = typeof(StringCollection);
			else if (wrappedField.IsArray)
				wrappedField = typeof(ArrayList);

			FieldBuilder fb = tb.DefineField(
				camel,
				wrappedField,
				FieldAttributes.Private
				);
			// add to table
			this.fieldBuilders.Add(field,fb);

		}

		private void GenerateProperty(TypeBuilder tb, FieldInfo field)
		{
			string capit = this.conformer.ToCapitalized(field.Name);
			FieldBuilder fb = this.fieldBuilders[field];

			OnPropertyBuild(tb.Name+"."+capit);

			// define property
			PropertyBuilder p = tb.DefineProperty(
				capit,
				PropertyAttributes.HasDefault,
				fb.FieldType,
				Type.EmptyTypes
				);

			if (this.builders.Contains(field.FieldType))
			{
				DecorateWrappedType(p,field);		
			}
			else
			{
				// check if XmlAttributeAttribute or XmlElement are present
				Object[] attributes = field.GetCustomAttributes(true);
				if (attributes.Length!=0)
				{
					foreach(Attribute a in field.GetCustomAttributes(true))
					{
						if (a.GetType()==typeof(XmlElementAttribute))
						{
							XmlElementAttribute el = (XmlElementAttribute)a;
							ConstructorInfo ci = TypeHelper.GetConstructor(
								typeof(XmlElementAttribute),typeof(string),typeof(Type));

							Type elType = el.Type;
							if (elType!=null && this.builders.Contains(elType))
								elType = this.builders[elType];

							CustomAttributeBuilder xmlEl = new CustomAttributeBuilder(
								ci,
								new Object[] {el.ElementName,elType} 
								);
							p.SetCustomAttribute(xmlEl);
						}
						else if (a.GetType()==typeof(XmlAttributeAttribute))
						{
							ConstructorInfo ci = TypeHelper.GetConstructor(
								typeof(XmlAttributeAttribute),typeof(string));
							CustomAttributeBuilder xmlEl = new CustomAttributeBuilder(
								ci,
								new Object[] {field.Name} 
								);
							p.SetCustomAttribute(xmlEl);
						}
						else if (a.GetType()==typeof(XmlTextAttribute))
						{
							ConstructorInfo ci = TypeHelper.GetDefaultConstructor(
								typeof(XmlTextAttribute));
							CustomAttributeBuilder xmlText = new CustomAttributeBuilder(
								ci,
								new Object[] {}
								);
							p.SetCustomAttribute(xmlText);
						}
					}
				}
				else
				{
					ConstructorInfo ci = TypeHelper.GetConstructor(
						typeof(XmlElementAttribute),typeof(string));
						CustomAttributeBuilder xmlAttr = new CustomAttributeBuilder(
						ci,
						new Object[] {field.Name} 
						);
					p.SetCustomAttribute(xmlAttr);
				}
			}

			// creates get/set methods
			p.SetGetMethod(GenerateGetMethod(tb,fb,capit));
			p.SetSetMethod(GenerateSetMethod(tb,fb,field,capit));
		}

		private void DecorateWrappedType(PropertyBuilder p, FieldInfo f)
		{
			ConstructorInfo ci = TypeHelper.GetConstructor(
				typeof(XmlElementAttribute),typeof(string));
			CustomAttributeBuilder xmlEl = new CustomAttributeBuilder(
				ci,
				new Object[] {f.Name} 
				);
			p.SetCustomAttribute(xmlEl);		
		}

		private MethodBuilder GenerateGetMethod(TypeBuilder tb, FieldBuilder fb, string name)
		{
			// create get method
			MethodBuilder gm = tb.DefineMethod("get_"+name,
				MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName,
				fb.FieldType,
				Type.EmptyTypes
				);
			ILGenerator getIL = gm.GetILGenerator();

			LocalBuilder V_0 = getIL.DeclareLocal(typeof(int));
			Label ret = getIL.DefineLabel();

			// if the value is null, allocate it
			getIL.Emit(OpCodes.Ldarg_0);
			getIL.Emit(OpCodes.Ldfld,fb);
			getIL.Emit(OpCodes.Stloc_0);
			getIL.Emit(OpCodes.Br_S,ret);
			getIL.MarkLabel(ret); 
			getIL.Emit(OpCodes.Ldloc_0);
			getIL.Emit(OpCodes.Ret);

			return gm;
		}

		private MethodBuilder GenerateSetMethod(
			TypeBuilder tb, 
			FieldBuilder fb, 
			FieldInfo field,
			string name
			)
		{
			// create set method
			MethodBuilder setm = tb.DefineMethod("set_" + name,
				MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName,    
				null,
				new Type[] { fb.FieldType }
				);

			ILGenerator setIL = setm.GetILGenerator();

			// check if nullable
			if (!TypeHelper.IsXmlNullable(field))
			{
				Label isTrue = setIL.DefineLabel();
				setIL.Emit(OpCodes.Ldarg_1);
				setIL.Emit(OpCodes.Brtrue_S,isTrue);
				setIL.Emit(OpCodes.Newobj, TypeHelper.GetDefaultConstructor(typeof(ArgumentNullException)));
				setIL.Emit(OpCodes.Throw);
				setIL.MarkLabel(isTrue);
			}

			setIL.Emit(OpCodes.Ldarg_0);
			setIL.Emit(OpCodes.Ldarg_1);
			setIL.Emit(OpCodes.Stfld, fb);
			setIL.Emit(OpCodes.Ret);

			return setm;
		}


		#endregion

	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Engineer
United States United States
Jonathan de Halleux is Civil Engineer in Applied Mathematics. He finished his PhD in 2004 in the rainy country of Belgium. After 2 years in the Common Language Runtime (i.e. .net), he is now working at Microsoft Research on Pex (http://research.microsoft.com/pex).

Comments and Discussions