Click here to Skip to main content
Click here to Skip to main content
Articles » Languages » XML » Utilities » Downloads
 
Add your own
alternative version

XML Schema Reader Writer Plugin for VS 2005/2008

, 17 Apr 2009 CPOL
Schema based XML reader writer implemented as .NET COM generator
Plugin_setup.zip
setup.exe
xsdAddinSetup.msi
Test_project.zip
jjjjjjjjjjjjj
Properties
Resources
Test.csproj.user
Test.suo
XmlSchemaAddon_src.zip
CodeWrapperGenerator
ICSharpCode.NRefactory.dll
Properties
PostInstaller
Properties
Resources
XmlSchemaAddon.AddIn
PrintLIBID
PrintLIBID.suo
Properties
XmlSchemaAddon.suo
XmlSchemaAddon
ICSharpCode.NRefactory.dll
Properties
Resources
XmlSchemaAddon.AddIn
XmlSchemaAddon.csproj.user
xsdAddinSetup
Release
xsdAddinSetup.vdproj
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.CodeDom;
using System.CodeDom.Compiler;
using ICSharpCode.NRefactory;
using ICSharpCode.NRefactory.Visitors;

namespace XmlSchemaWrapperGen
{
    /// <summary>
    /// Generates wrappers class for auto generated
    /// proxy classes made from xsd.exe tool.
    /// </summary>
    public class InterfaceGenerator
    {
        /// <summary>
        /// List of all the classes defined in the schema;
        /// </summary>
        List<string> m_schemaClassList = new List<string>();

        /// <summary>
        /// Guid for the factory interface
        /// </summary>
        Guid m_factoryInterfaceGuid;
     

        /// <summary>
        /// Generates wrappers COM interface for auto generated
        /// XML proxy classes made from xsd.exe tool.
        /// </summary>
        /// <param name="proxyFileCode">auto generated proxy file content
        /// made from xsd.exe tool
        /// </param>
        /// <param name="language">The code language</param>
        /// <param name="outputFilepath">Path to the file that will be created and contains the created interface code.</param>
        /// <param name="factoryGuid">The COM guid to set in the generated factory interface</param>
        /// <remarks>
        /// The given proxy file content must be in VB.NET or C# language, and .NET 2.0 compatible
        /// </remarks>
        public void CreateComWrapperInterface(TextReader proxyFileCode, SupportedLanguage language, Guid factoryGuid, string outputFilepath)
        {
            if (proxyFileCode == null) throw new ArgumentNullException("proxyFileCode");
            m_factoryInterfaceGuid = factoryGuid;

            m_schemaClassList.Clear();
            string proxyCode = proxyFileCode.ReadToEnd();
            string rootClassName = GetRootElementClassName(new StringReader(proxyCode));
            if (string.IsNullOrEmpty(rootClassName))
                throw new ArgumentException("Failed locate the class that describes the XML root element in the code file", "proxyFile");

            TextReader reader = new StringReader(proxyCode);
            CodeNamespace ns = Parse(reader, language).Namespaces[0];
            CodeNamespace newInterfacesNs = TransformToInterfaces(ns, rootClassName);
            WriteCode(newInterfacesNs, language, outputFilepath);

        }

        /// <summary>
        /// Generates wrappers COM interface for auto generated
        /// XML proxy classes made from xsd.exe tool.
        /// </summary>
        /// <param name="proxyFileCode">auto generated proxy file content
        /// made from xsd.exe tool
        /// </param>
        /// <param name="language">The code language</param>
        /// <param name="factoryGuid">The COM guid to set in the generated factory interface</param>
        /// <returns>Namespace contains the code of the interface</returns>
        /// <remarks>
        /// The given proxy file content must be in VB.NET or C# language, and .NET 2.0 compatible
        /// </remarks>
        public CodeNamespace CreateComWrapperInterface(TextReader proxyFileCode, SupportedLanguage language, Guid factoryGuid)
        {
            if (proxyFileCode == null) throw new ArgumentNullException("proxyFileCode");

            m_factoryInterfaceGuid = factoryGuid;
            m_schemaClassList.Clear();
            string proxyCode = proxyFileCode.ReadToEnd();
            string rootClassName = GetRootElementClassName(new StringReader(proxyCode));
            if (string.IsNullOrEmpty(rootClassName))
                throw new ArgumentException("Failed locate the class that describes the XML root element in the code file", "proxyFile");

            TextReader reader = new StringReader(proxyCode);
            CodeNamespace ns = Parse(reader, language).Namespaces[0];
            return TransformToInterfaces(ns, rootClassName);
        }

        /// <summary>
        /// Returns the name of the root element class from
        /// xsd.exe auto generated code.
        /// </summary>
        /// <param name="schemaCodeReader">Reader to xsd.exe auto generated code</param>
        /// <returns>
        /// The name of the root element class, or null if no root alement was detected
        /// </returns>
        private string GetRootElementClassName( TextReader xsdSchemaCodeReader )
        {
            string xsdCode = xsdSchemaCodeReader.ReadToEnd();
            //finding close index to the class that is the owner of "XmlRootAttribute";
            int index = xsdCode.IndexOf("XmlRootAttribute");
            if( index < 0 ) return null;
            index = xsdCode.IndexOf(" class ", index);
            if( index < 0 ) return null;

            string abcStr = "abcdefghijklmnopqrstuvwxyz";

            int nameStartIndex = xsdCode.IndexOfAny(abcStr.ToCharArray(), index + " class ".Length);
            int nameStartIndex2 = xsdCode.IndexOfAny(abcStr.ToUpper().ToCharArray(), index + " class ".Length);
            if( nameStartIndex > 0 && nameStartIndex2 > 0  )
                nameStartIndex = Math.Min(nameStartIndex, nameStartIndex2);
            else 
                nameStartIndex = Math.Max(nameStartIndex, nameStartIndex2);

            char[] stopChars = new char[]{' ',';',':'};
            int nameLastIndex = xsdCode.IndexOfAny(stopChars, nameStartIndex);
            string className = xsdCode.Substring(nameStartIndex, nameLastIndex - nameStartIndex);
            return className;
        }


        /// <summary>
        /// Writes the given code to a file
        /// </summary>
        /// <param name="code">The code to write</param>
        /// <param name="language">The code language</param>
        /// <param name="filePath">The output file path</param>
        private void WriteCode( CodeNamespace code, SupportedLanguage language, string filePath)
        {
            string strLang = language == SupportedLanguage.CSharp ? "CSharp" : "VisualBasic";
            CodeDomProvider cp = CodeDomProvider.CreateProvider(strLang);
            CodeGeneratorOptions opt = new CodeGeneratorOptions();
            opt.BlankLinesBetweenMembers = true;
            opt.ElseOnClosing = false;
            opt.VerbatimOrder = true;
            StreamWriter sw = new StreamWriter(filePath);
            try
            {
                cp.GenerateCodeFromNamespace(code, sw, opt);
            }
            finally{
                sw.Close();
            }
        }

        /// <summary>
        /// Attempts to parse a code stream into <see cref="CodeCompileUnit"/>
        /// </summary>
        /// <param name="reader">Reader to read from the code to parse</param>
        /// <param name="language">The programming language that the code is written at</param>
        /// <returns><see cref="CodeCompileUnit"/> that contains the parsed code.</returns>
        /// <exception cref="InvalidDataException">Thrown when parsing the code failed.</exception>
        private CodeCompileUnit Parse(System.IO.TextReader reader, SupportedLanguage language)
        {
            IParser parser = ParserFactory.CreateParser(language, reader);
            parser.Parse();

            if (parser.Errors.Count > 0)
            {
                string errMsg = string.Format("{0} errors detected while parsing the code stream: \r\n{1}", parser.Errors.Count, parser.Errors.ErrorOutput);
                throw new InvalidDataException(errMsg);
            }

            CodeDomVisitor visit = new CodeDomVisitor();
            visit.VisitCompilationUnit(parser.CompilationUnit, null);

            // Remove Unused Namespaces
            for (int i = 0; i < visit.codeCompileUnit.Namespaces.Count; ++i)
            {
                if (visit.codeCompileUnit.Namespaces[i].Types.Count == 0)
                    visit.codeCompileUnit.Namespaces.RemoveAt(i);
            }

            return visit.codeCompileUnit;
        }

        /// <summary>
        /// Accepts XML schema proxy code generated by xsd.exe tool
        /// and returns new code with corresponding interfaces
        /// </summary>
        /// <param name="proxyNs">XML schema proxy code generated by xsd.exe tool</param>
        /// <param name="rootClassName">
        /// The name of the class in the given code, that represent the root element in XML.
        /// </param>
        /// <returns>Code with corresponding interfaces</returns>
        private CodeNamespace TransformToInterfaces(CodeNamespace proxyNs, string rootClassName)
        {
            CodeNamespace ns = new CodeNamespace(proxyNs.Name + ".ComXml.Interfaces");
//            ns.Imports.Add( new CodeNamespaceImport(proxyNs.Name) );

            CodeTypeDeclaration validator = AddValidatorInterface(ns);
            CodeTypeDeclaration reader = CreateObjectReaderInterface( "I" + rootClassName );
            CodeTypeDeclaration writer = CreateObjectWriterInterface("I" + rootClassName);
            CodeTypeDeclaration factory = CreateFactoryInterface(reader, writer, validator);
            ns.Types.Add(factory);

            ns.Types.AddRange( new CodeTypeDeclaration[] {reader, writer});
            foreach (CodeTypeDeclaration type in proxyNs.Types)
            {
                if (!type.IsClass) continue;
                m_schemaClassList.Add(type.Name);
            }

            foreach (CodeTypeDeclaration type in proxyNs.Types)
            {
                if( type.IsClass ){
                    CodeTypeDeclaration createdInterface = GenerateComInterface(type);
                    ns.Types.Add(createdInterface);
                    AddFactoryCreationMthd(factory, createdInterface);
                }
                else if (type.IsEnum){
                    AddComAttributes(type);
                    ns.Types.Add(type);
                }
            }  

            return ns;
        }


        /// <summary>
        /// Creates factory interface with methods to create XML writer and XML writer
        /// </summary>
        /// <param name="reader">The object reader interface</param>
        /// <param name="writer">The object writer interface</param>
        /// <param name="validator">The validator interface</param>
        /// <returns>The factory that was created</returns>
        private CodeTypeDeclaration CreateFactoryInterface(CodeTypeDeclaration reader, CodeTypeDeclaration writer, CodeTypeDeclaration validator)
        {
            CodeTypeDeclaration factory = new CodeTypeDeclaration("IXmlFactory");
            factory.IsInterface = true;
            factory.Attributes = MemberAttributes.Public;
            factory.Comments.Add(new CodeCommentStatement("<summary>Interface for constructing XML reading and writing objects according to a specific schema.</summary>", true));
            AddComAttributes(factory);

            CodeMemberMethod method = new CodeMemberMethod();
            method.Name = "CreateWriter";
            method.ReturnType = new CodeTypeReference(writer.Name);
            
            method.Comments.Add(new CodeCommentStatement("<summary>Creates XML objects writer according to a specific schema.</summary>", true));
            method.Comments.Add(new CodeCommentStatement("<returns>The created writer.</returns>", true));
            factory.Members.Add(method);

            method = new CodeMemberMethod();
            method.Name = "CreateReader";
            method.ReturnType = new CodeTypeReference(reader.Name);
            method.Comments.Add(new CodeCommentStatement("<summary>Creates XML objects reader according to a specific schema.</summary>", true));
            method.Comments.Add(new CodeCommentStatement("<returns>The created reader.</returns>", true));
            factory.Members.Add(method);

            method = new CodeMemberMethod();
            method.Name = "CreateReader";
            method.ReturnType = new CodeTypeReference(reader.Name);
            method.Parameters.Add(new CodeParameterDeclarationExpression(validator.Name, "validationHandler"));
            method.Comments.Add(new CodeCommentStatement("<summary>Creates XML objects writer according to a specific schema with validation handler.</summary>", true));
            method.Comments.Add(new CodeCommentStatement("<param name=\"validationHandler\">Validation events handler</param>", true));
            method.Comments.Add(new CodeCommentStatement("<returns>The created reader.</returns>", true));
            factory.Members.Add(method);
            return factory;
        }

        /// <summary>
        /// Add object creation method to a factory interface.
        /// </summary>
        /// <param name="factory">The factory to add the method to</param>
        /// <param name="typeToCreate">The type of interface that the method should return.</param>
        private void AddFactoryCreationMthd( CodeTypeDeclaration factory,  CodeTypeDeclaration typeToCreate)
        {
            //Remove the 'I' prefix from the type name
            string typeName = typeToCreate.Name.Substring(1);

            CodeMemberMethod method = new CodeMemberMethod();
            method.Name = "Create" + typeName;
            method.ReturnType = new CodeTypeReference(typeToCreate.Name);
            method.Comments.Add(new CodeCommentStatement("<summary>Creates and returns " + typeToCreate.Name + "</summary>", true));
            method.Comments.Add(new CodeCommentStatement("<returns>The created "+ typeToCreate.Name +".</returns>", true));
            factory.Members.Add(method);
        }

        /// <summary>
        /// Adds XML validator interface to the given namespace
        /// </summary>
        /// <param name="codeNs">Namespace to add the validator interface to</param>
        /// <returns>The validator interface that was added</returns>
        private CodeTypeDeclaration AddValidatorInterface( CodeNamespace codeNs )
        {
            CodeTypeDeclaration severity = new CodeTypeDeclaration("ValidationSeverity");
            severity.IsEnum = true;
            severity.Attributes = MemberAttributes.Public;
            severity.Comments.Add(new CodeCommentStatement("<summary>Represents the severity of the validation event</summary>", true));
                
            CodeMemberField enumField = new CodeMemberField(typeof(int), "Error");
            enumField.Comments.Add(new CodeCommentStatement("<summary>Indicates a validation error occurred when validating the instance document. This applies to document type definitions (DTDs) and XML Schema definition language (XSD) schemas. The World Wide Web Consortium (W3C) validity constraints are considered errors. If no validation event handler has been created, errors throw an exception.</summary>", true));
            severity.Members.Add(enumField);
            enumField = new CodeMemberField("","Warning");
            enumField.Comments.Add(new CodeCommentStatement("<summary>Indicates that a validation event occurred that is not an error. A warning is typically issued when there is no DTD, or XML Schema to validate a particular element or attribute against. Unlike errors, warnings do not throw an exception if there is no validation event handler.</summary>", true));
            severity.Members.Add(enumField);
            AddComAttributes(severity);
            codeNs.Types.Add(severity);

            CodeTypeDeclaration validator = new CodeTypeDeclaration("IXmlValidator");
            validator.IsInterface = true;
            validator.Attributes = MemberAttributes.Public;
            validator.Comments.Add(new CodeCommentStatement("<summary>Interface for an object that accepts XML validation events notification.</summary>", true));
            AddComAttributes(validator);

            CodeMemberMethod method = new CodeMemberMethod();
            method.Name = "OnXmlSchemaValidationError";
            method.Comments.Add(new CodeCommentStatement("<summary>This method is called whenever XML validation event occurs.</summary>", true));
            method.Comments.Add(new CodeCommentStatement("<param name=\"message\">Validation message in human language, specify the event</param>", true));
            method.Comments.Add(new CodeCommentStatement("<param name=\"severityLevel\">The severity level of the validation event, that can be warning or error</param>", true));
            method.Parameters.Add(new CodeParameterDeclarationExpression(typeof(string), "message"));
            method.Parameters.Add(new CodeParameterDeclarationExpression(severity.Name, "severityLevel"));
            validator.Members.Add(method);
            codeNs.Types.Add(validator);
            return validator;

        }


        /// <summary>
        /// Creates and returns XML reader objects that reads
        /// objects from XML file or stream.
        /// </summary>
        /// <param name="readingTypeName">The name of the type that the reader should return.</param>
        /// <returns>XML reader objects that reads XML stream according to schema</returns>
        private CodeTypeDeclaration CreateObjectReaderInterface(string readingTypeName)
        {
            CodeTypeDeclaration reader = new CodeTypeDeclaration("IXmlReader");
            reader.IsInterface = true;
            reader.Attributes = MemberAttributes.Public;
            reader.Comments.Add(new CodeCommentStatement("<summary>XML reader that reads "+readingTypeName+" XML objects.</summary>", true));
            AddComAttributes(reader);

            CodeMemberMethod method = new CodeMemberMethod();
            method.Name = "ReadFromFile";
            method.Parameters.Add(new CodeParameterDeclarationExpression(typeof(string), "xmlFilePath"));
            method.ReturnType = new CodeTypeReference(readingTypeName);
            method.Comments.Add(new CodeCommentStatement("<summary>",true));
            method.Comments.Add(new CodeCommentStatement("Reads XML objects that implements the " + readingTypeName + " interface, from XML file.", true));
            method.Comments.Add(new CodeCommentStatement("</summary>", true));
            method.Comments.Add(new CodeCommentStatement("<param name=\"xmlFilePath\">Path to the XML file to read.</param>", true));
            method.Comments.Add(new CodeCommentStatement("<returns>The read object or null if reading failed.</returns>", true));
            reader.Members.Add(method);

            method = new CodeMemberMethod();
            method.Name = "ReadFromStream";
            method.Parameters.Add(new CodeParameterDeclarationExpression(typeof(string), "xmlStream"));
            method.ReturnType = new CodeTypeReference(readingTypeName);
            method.Comments.Add(new CodeCommentStatement("<summary>", true));
            method.Comments.Add(new CodeCommentStatement("Reads XML objects that implements the " + readingTypeName + " interface, from XML string.", true));
            method.Comments.Add(new CodeCommentStatement("</summary>", true));
            method.Comments.Add(new CodeCommentStatement("<param name=\"xmlStream\">String that contains the XML to read from.</param>", true));
            method.Comments.Add(new CodeCommentStatement("<returns>The read object or null if reading failed.</returns>", true));
            reader.Members.Add(method);

            CodeMemberProperty prop = new CodeMemberProperty();
            prop.Name = "HasValidtionHandler";
            prop.Type = new CodeTypeReference(typeof(bool));
            prop.HasGet = true;
            prop.Comments.Add(new CodeCommentStatement("<summary>Gets whether or not the reader has registered validator</summary>", true));
            reader.Members.Add(prop);

            prop = new CodeMemberProperty();
            prop.Name = "Schema";
            prop.Type = new CodeTypeReference(typeof(string));
            prop.HasGet = true;
            prop.Comments.Add(new CodeCommentStatement("<summary>Gets the XML schema, that the reader usesd.</summary>", true));
            reader.Members.Add(prop);
            return reader;
        }


        /// <summary>
        /// Creates and returns XML writer objects that writes
        /// objects to XML file or stream.
        /// </summary>
        /// <param name="readingTypeName">The name of the type that the writer should return.</param>
        /// <returns>XML writer objects that writes XML stream according schema</returns>
        private CodeTypeDeclaration CreateObjectWriterInterface(string WritingTypeName)
        {
            CodeTypeDeclaration writer = new CodeTypeDeclaration("IXmlWriter");
            writer.IsInterface = true;
            writer.Attributes = MemberAttributes.Public;
            writer.Comments.Add(new CodeCommentStatement("<summary>XML writer that writes " + WritingTypeName + " XML objects.</summary>", true));
            AddComAttributes(writer);

            CodeMemberMethod method = new CodeMemberMethod();
            method.Name = "WriteToFile";
            method.Parameters.Add(new CodeParameterDeclarationExpression(typeof(string), "outXmlFilePath"));
            method.Parameters.Add(new CodeParameterDeclarationExpression( WritingTypeName, "objectToWrite"));
            method.Comments.Add(new CodeCommentStatement("<summary>", true));
            method.Comments.Add(new CodeCommentStatement("Writes the given " + WritingTypeName + " to XML file.", true));
            method.Comments.Add(new CodeCommentStatement("</summary>", true));
            method.Comments.Add(new CodeCommentStatement("<param name=\"outXmlFilePath\">Path to the XML file to write to.</param>", true));
            method.Comments.Add(new CodeCommentStatement("<param name=\"objectToWrite\">The XML object to write into the file.</param>", true));
            writer.Members.Add(method);

            method = new CodeMemberMethod();
            method.Name = "WriteToStream";
            method.Parameters.Add(new CodeParameterDeclarationExpression(typeof(string), "outXmlStream"));
            method.Parameters[0].Direction = FieldDirection.Out;
            method.Parameters.Add(new CodeParameterDeclarationExpression(WritingTypeName, "objectToWrite"));
            method.Comments.Add(new CodeCommentStatement("<summary>", true));
            method.Comments.Add(new CodeCommentStatement("Writes the given " + WritingTypeName + " to XML string.", true));
            method.Comments.Add(new CodeCommentStatement("</summary>", true));
            method.Comments.Add(new CodeCommentStatement("<param name=\"outXmlStream\">String to write the object into.</param>", true));
            method.Comments.Add(new CodeCommentStatement("<param name=\"objectToWrite\">The XML object to write into the string.</param>", true));
            writer.Members.Add(method);

            return writer;
        }


        /// <summary>
        /// Generates COM interface type form the given class type.
        /// </summary>
        /// <param name="classType">
        /// XML schema proxy class type generated by xsd.exe tool
        /// </param>
        /// <returns>Interface type</returns>
        /// <remarks>
        /// This method assumes that the type given is generated from xsd.exe tool
        /// thus, have a public get & set property for each data member 
        /// </remarks>
        private CodeTypeDeclaration GenerateComInterface(CodeTypeDeclaration classType)
        {
            CodeTypeDeclaration interfaceType = new CodeTypeDeclaration("I" + classType.Name);
            interfaceType.IsInterface = true;
            interfaceType.Attributes = MemberAttributes.Public;
            AddComAttributes(interfaceType);

            //Duplicate the properties
            foreach (CodeTypeMember member in classType.Members)
            {
                if (member == null || !(member is CodeMemberProperty)) continue;
                CodeMemberProperty property = member as CodeMemberProperty;
                CodeMemberProperty newProp = new CodeMemberProperty();
                if (m_schemaClassList.Contains(property.Type.BaseType))
                    newProp.Type = new CodeTypeReference("I" + property.Type.BaseType);
                else
                    newProp.Type = property.Type;

                newProp.Type.ArrayRank = property.Type.ArrayRank;
                newProp.Name = property.Name;
                newProp.HasSet = newProp.HasGet = true;
                interfaceType.Members.Add(newProp);
            }

            return interfaceType;
        }

        

        /// <summary>
        /// Adds COM attributes to the given code type(Class or Interface)
        /// </summary>
        /// <param name="type">The class or interface to add COM attributes to</param>
        private void AddComAttributes(CodeTypeDeclaration type)
        {
            if (!type.IsInterface && !type.IsClass && !type.IsEnum)
                throw new ArgumentException("Cannot add COM attributes to non class or interface", "type");

            CodeAttributeDeclaration guidAttr = new CodeAttributeDeclaration(
                new CodeTypeReference(typeof(System.Runtime.InteropServices.GuidAttribute))
                );

            CodeExpression exp = null; 
            if( type.Name != "IXmlFactory" )
                exp = new CodePrimitiveExpression(Guid.NewGuid().ToString().ToUpper());
            else
                exp = new CodePrimitiveExpression(m_factoryInterfaceGuid.ToString());

            guidAttr.Arguments.Add(new CodeAttributeArgument(exp));
            type.CustomAttributes.Add(guidAttr);

            CodeAttributeDeclaration comVisibleAttr = new CodeAttributeDeclaration(
                new CodeTypeReference(typeof(System.Runtime.InteropServices.ComVisibleAttribute))
                );

            exp = new CodePrimitiveExpression(true);
            comVisibleAttr.Arguments.Add(new CodeAttributeArgument(exp));
            type.CustomAttributes.Add(comVisibleAttr);

            if (type.IsInterface)
            {
                CodeAttributeDeclaration comInterfaceTypeAttr = new CodeAttributeDeclaration(
                     new CodeTypeReference(typeof(System.Runtime.InteropServices.InterfaceTypeAttribute))
                    );

                CodePropertyReferenceExpression enumRef = new CodePropertyReferenceExpression(
                        new CodeVariableReferenceExpression("System.Runtime.InteropServices.ComInterfaceType") ,"InterfaceIsIUnknown"
                        );

                comInterfaceTypeAttr.Arguments.Add(new CodeAttributeArgument(enumRef));
                type.CustomAttributes.Add(comInterfaceTypeAttr);
            }            
        }
    }
}

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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

JadBenAutho
Software Developer Rontal Applications
Israel Israel
Born and raised in Israel, I've caught the programming virus at the age of 15.
Since than I can't stop coding.

| Advertise | Privacy | Mobile
Web04 | 2.8.141015.1 | Last Updated 17 Apr 2009
Article Copyright 2009 by JadBenAutho
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid