|
|||||||||||||||||||||||
|
|||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
If you like this tool, support it by voting, if you don't like it, make your vote verbose... Pre-IntroductionThe XsdTidy tool has been entirely rebuild from scratch using CodeDom which is much easier to handle than Emit. The new version is called Refly and available at the Refly article. IntroductionXsdTidy is a refactoring tool to overcome some silly limitations of the exceptional Xsd.exe (see [1]) tool provided with the .NET framework. More specifically, XsdTidy addresses the following problems:
XsdTidy achieves refactoring by recreating new classes for each type exported by the Xsd.exe tool using the As a nice application of the tool, a full .NET wrapper of the DocBook schema (see [3]) is provided with the project. This .NET wrapper lets write or generate DocBook XML easily with the help of Intellisense. Fixing problemsName conversionThe .NET standards define specific naming convention for all types of data: arguments should be camel case, function names capitalized, etc... This is really helpful to keep the framework consistent. Tools like FxCop help us stay on the "normalized" side. This problem is tackled the dumb way: given a dictionary of "common" words, the class There is much room for improvement on the list of words and the algorithm to split the name, any contribution welcome. FixedArraySizeArrays are replaced by PropertiesFields are hidden in properties, which is more convenient to use. Moreover, collection fields do not have set property according to FxCop rule. public class testclass
{
[XmlElement("values",typeof(int)]
public int[] values;
}
becomes: public class TestClass
{
private ArrayList values;
[XmlElement("values",typeof(int)]
public ArrayList Values
{
get
{
return this.values;
}
}
}
System.Reflection.EmitThe What is Emit?The // emit
ILGenerator il = ...;
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Stfld, fb);
il.Emit(OpCodes.Ret);
// C# equivalent
this.fb = value;
If you are a newcomer, it can look cryptic, but we'll try to explain the above a bit. Where to start ?The problem with Hopefully, creating this model is easy! It is possible, using decompilers such as Reflector (see [4]), to read the IL code of any .NET assembly. The idea is simple: open a dummy project where you create the model class that needs to be factored, compile and use a decompiler to read the IL of your model and there you go...you have IL code!
Emit, the basicsI will cover some very basic facts about using if (value==null)
throw new ArgumentNullException("value");
this.field=value;
Getting a ILGeneratorUsually, you start by creating an MethodBuilder mb = ...;
ILGenerator il = mb.GetGenerator();
OpCodesThe ArgumentsEach time you call a method (static or non-static), the method arguments are accessible through Labels
Label isTrue = il.DefineLabel();
Once the il.MarkLabel(isTrue);
Comparing values to nullComparing a value to null is done using the Label isTrue = il.DefineLabel();
il.Emit(OpCodes.Ldarg_1); // pushing value on the stack
il.Emit(OpCodes.Brtrue_S,isTrue); // if non null, jump to label
// IL code to throw an exception here
...
// marking label
il.MarkLabel(isTrue);
...
Creating objectsTo create object, you must first retrieve the ConstructorInfo ci =
typeof(ArgumentNullException).GetConstructor(Type.EmptyTypes);
Label isTrue = il.DefineLabel();
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Brtrue_S,isTrue);
il.Emit(OpCodes.NewObj,ci); // creating new exception
il.Emit(OpCodes.Throw); // throwing the exception
il.MarkLabel(isTrue);
...
You can clearly see the "jump across the exception" with the label Assigning fieldsThe last step is to assign the field with the value (stored in the first argument). To do so, we need to push the " // Type t is the class type
FieldInfo fi = t.GetField("field");
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Stdfld,fi);
Finish the workTo close a method, use il.Emit(OpCodes.Ret);
RefactoringMain stepsThe refactoring is handled by the
During the process of factoring, special care is taken about nullable/non nullable types and collection handling:
Once the factoring is finished, the types are created and saved to an assembly. Using XsdWrappedGeneratorThe XsdWrappedGenerator encapsulates all the "wrapping" functionalities: create a new instance, add the types you need to be refactored and save the result to a file: XsdWrapperGenerator gen = new XsdWrapperGenerator(
"CodeWrapper", // output namespace and assembly name
new Version(1.0), // outputed assembly version
);
// adding types
gen.AddClass( typeof(myclass) );
...
// refactor
gen.WrapClasses();
// save to file, this invalidates gen.
gen.Save();
The name passed to the constructor is used as default namespace and output assembly name. Using the Command Line applicationXsdWrapperGenerator comes with a minimal console application that loads an assembly, searches to types, refactors them and output the results. Calling convention is as follows: XsdTidy.Cons.exe AssemblyName WrappedClassNamespace OutputNamespace Version
where
NDocBookDocBook is an XML standard to describe a document. It is a very powerful tool since the same XML source can be rendered in almost all possible output formats: HTML, CHM, PDF, etc... This richness comes to a price: DocBook is complicated for the beginner and it tends to be XML-ish. This was the starting of the article for me: I needed to generate DocBook XML to automatically generate code in GUnit (see [5]) but I wanted to take advantage of VS intellisense. The first step was to generate the .NET classes mapping the DocBook schema using the Xsd.exe tool. The generated code had some problems that would make it unusable: non-nullable fields where not initialized automatically and this would lead to a lot of manual work. Hence, the second was to write XsdTidy and apply it to DocBook. So here's an example of use: // creating a book object
Book b = new Book();
// title is nullable, so we must allocate it
b.Title = new Title();
// text is a collection, preallocated
b.Title.Text.Add("My first book");
// nullable
b.Subtitle = new Subtitle();
b.Subtitle.Text.Add("A subtitle");
Toc toc = new Toc();
b.Items.Add(toc);
toc.Title = new Title();
toc.Title.Text.Add("What a Toc!");
Part part = new Part();
b.Items.Add(part);
part.Title = new Title();
part.Title.Text.Add("My first page");
// generate xml using XmlSerialization tools
using (StreamWriter sw = new StreamWriter("mybook.xml"))
{
XmlTextWriter writer = new XmlTextWriter(sw);
writer.Formatting = Formatting.Indented;
XmlSerializer ser = new XmlSerializer(typeof(Book));
ser.Serialize(writer,b);
}
The output of this snippet looks like this: <?xml version="1.0" encoding="utf-8"?>
<book xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<title>My first book</title>
<subtitle>A subtitle</subtitle>
<toc>
<title>What a Toc!</title>
</toc>
<part>
<title>My first page</title>
</part>
</book>
Now, with Intellisense on our side, I am much more comfortable with DocBook... Conclusion
History
References
|
||||||||||||||||||||||