Testing

webTiger Logo Wide

Generating In-Code Representations Of An XML Schema

Generating an in-code representation of an XML schema is pretty straight-forward, using native classes in the .NET Framework. This article covers how to read the XML, and generate the appropriate class files to support that.

The code below will produce a file that contains classes that represent your XML schema.

// Load the XmlSchema and its collection.
XmlSchema schema;
XmlSchemaSet schemaSet;

using (FileStream fs = new FileStream(@"c:\mySchema.xsd", FileMode.Open))
{
    schema = XmlSchema.Read(fs, null);

    schemaSet = new XmlSchemaSet();
    schemaSet.Add(schema);
    schemaSet.Compile();
}

schemaNamespace = schema.TargetNamespace;

XmlSchemas schemas = new XmlSchemas { schema };

// Create the importer for these schemas.
XmlSchemaImporter importer = new XmlSchemaImporter(schemas);

// System.CodeDom namespace for the XmlCodeExporter to put classes in.
CodeNamespace code = new CodeNamespace(targetNamespace);
XmlCodeExporter exporter = new XmlCodeExporter(code);

// Iterate schema top-level elements and export code for each.
foreach (XmlSchemaElement element in schema.Elements.Values)
{
    // Import the mapping first.
    XmlTypeMapping mapping = importer.ImportTypeMapping(element.QualifiedName);

    // Export the code finally.
    exporter.ExportTypeMapping(mapping);
}

// Create the appropriate generator for the language.
CodeDomProvider provider = new CSharpCodeProvider();

//Could support VB instead with: CodeDomProvider provider = new Microsoft.VisualBasic.VBCodeProvider();

using (StreamWriter writer = new StreamWriter(@"c:\myCode.cs", false))
{
    writer.WriteLine("using System;");
    writer.WriteLine();
    writer.WriteLine(
        "/* This class was created by an automatic code generation tool. */");
    writer.WriteLine();
    provider.GenerateCodeFromNamespace(code, writer, new CodeGeneratorOptions());
}Code language: C# (cs)

Although the above code does produce a file that contains classes that represent your XML schema, without further tweaking this set of classes won’t include any documentation comments. It is relatively easy to update the code namespace to include documentation comments, based on the annotations in the schema.

There are comments properties for each of the containers in the code namespace, so adding the comments simply becomes a case of iterating through the code namespace, matching the types to elements in the schema, and finally writing any annotation/documentation information to the Comments property. It is a fairly large amount of code though, due to the different types of elements XML defines.

NOTE: The code below may not provide exhaustive commenting coverage. This is because it is based on my needs for a particular project at the time of authoring.

public static void UpdateComments(code code, XmlSchema schema)
{
    string comments;

    foreach (CodeTypeDeclaration type in code.Types)
    {
        comments = GetCommentsFromSchema(schema, type.Name);

        type.Comments.Clear();
        type.Comments.Add(
            new CodeCommentStatement(
                string.Format("<summary>{0}</summary>", 
                comments.Trim().Replace("\r", "").Replace("\n", " ")), 
                true));

        foreach (CodeTypeMember member in type.Members)
        {
            comments = GetCommentsFromSchema(schema, type.Name, member.Name);

            member.Comments.Clear();
            member.Comments.Add(
                new CodeCommentStatement(string.Format(
                    "<summary>{0}</summary>", 
                    comments.Trim().Replace("\r", "").Replace("\n", " ")), 
                    true));
        }
    }
}

private static string GetCommentsFromSchema(
    XmlSchema schema, string typeName, string memberName = "")
{
    XmlSchemaElement element;
    XmlSchemaComplexType complex;
    XmlSchemaSimpleType simple;
    XmlSchemaDocumentation documentation;
    string comments = null;

    foreach (XmlSchemaObject item in schema.Items)
    {
        simple = item as XmlSchemaSimpleType;
        element = item as XmlSchemaElement;

        if (simple != null && simple.Name == typeName)
        {
            comments = GetCommentsFromSimpleType(simple, memberName);
        }

        if (element != null && element.Name == typeName)
        {
            // Matching type found, so now process the detail...

            if (!string.IsNullOrWhiteSpace(memberName))
            {
                complex = element.ElementSchemaType as XmlSchemaComplexType;

                if (complex != null)
                {
                    comments = GetCommentsFromComplexType(complex, memberName);
                }

                break;
            }

            // Looking for documentation for the type itself, 
            // so return them if available.

            comments = GetCommentsFromElement(element);
        }
    }

    if (!string.IsNullOrEmpty(comments))
    {
        StringBuilder output = new StringBuilder(comments.Length);
        comments = comments
            .Replace("\r", " ")
            .Replace("\n", " ")
            .Replace("<", "<")
            .Replace(">", ">")
            .Trim();

        bool previousWasSpace = false;

        foreach (char character in comments)
        {
            if (character == ' ')
            {
                if (previousWasSpace)
                {
                    continue; // stop regions of whitespace.
                }

                output.Append(character);
                previousWasSpace = true;
            }
            else
            {
                output.Append(character);
                previousWasSpace = false;
            }
        }

        return output.ToString();
    }

    return "";
}

private static string GetCommentsFromElement(XmlSchemaElement element)
{
    XmlSchemaDocumentation documentation;

    if (element != null && 
        element.Annotation != null && 
        element.Annotation.Items != null && 
        element.Annotation.Items.Count > 0)
    {
        foreach (XmlSchemaObject elementItem in element.Annotation.Items)
        {
            documentation = elementItem as XmlSchemaDocumentation;

            if (documentation != null && documentation.Markup.Length > 0)
            {
                return documentation.Markup[0].Value;
            }
        }
    }

    if (element != null && 
        string.IsNullOrEmpty(element.Name) && 
        element.RefName != null && 
        !string.IsNullOrEmpty(element.RefName.Name))
    {
        if (element.MaxOccurs == 1)
        {
            return string.Format(
                "The {0} sub-element details.", element.RefName.Name);
        }

        return string.Format(
            "The collection of {0} sub-element values.", element.RefName.Name);
    }    

    return null;
}

private static string GetCommentsFromComplexType(
    XmlSchemaComplexType complex, string memberName)
{
    XmlSchemaAttribute attribute;
    XmlSchemaDocumentation documentation;

    foreach (XmlSchemaObject entry in complex.Attributes)
    {
        attribute = entry as XmlSchemaAttribute;

        if (attribute != null && 
            attribute.Name.Equals(memberName, StringComparison.OrdinalIgnoreCase) &&
            attribute.Annotation != null && 
            attribute.Annotation.Items != null && 
            attribute.Annotation.Items.Count > 0)
        {
            foreach (XmlSchemaObject elementItem in attribute.Annotation.Items)
            {
                documentation = elementItem as XmlSchemaDocumentation;

                if (documentation != null && documentation.Markup.Length > 0)
                {
                    return documentation.Markup[0].Value;
                }
            }
        }
    }

    // No documentation found, so check if the member is defined in a 
    // sequence, choice, etc.

    XmlSchemaGroupRef group = complex.Particle as XmlSchemaGroupRef;
    XmlSchemaChoice choice = complex.Particle as XmlSchemaChoice;
    XmlSchemaAll all = complex.Particle as XmlSchemaAll;
    XmlSchemaSequence sequence = complex.Particle as XmlSchemaSequence;

    if (choice != null)
    {
        return GetCommentsFromChoice(choice, memberName);
    }

    if (group != null)
    {
        return GetCommentsFromGroupRef(group, memberName);
    }

    if (all != null)
    {
        return GetCommentsFromAll(all, memberName);
    }

    if (sequence != null)
    {
        return GetCommentsFromSequence(sequence, memberName);
    }

    return null;
}

private static string GetCommentsFromSequence(
    XmlSchemaSequence compositor, string memberName)
{
    if (compositor == null || compositor.Items == null || 
        compositor.Items.Count == 0 || string.IsNullOrWhiteSpace(memberName))
    {
        return null;
    }

    XmlSchemaElement element;
    XmlSchemaGroupRef group;
    XmlSchemaChoice choice;
    XmlSchemaAny any;
    XmlSchemaSequence sequence;
    string comments = null;

    foreach (XmlSchemaObject item in compositor.Items)
    {
        element = item as XmlSchemaElement;

        if (element != null &&
            ((element.Name != null && 
            element.Name.Equals(memberName, StringComparison.OrdinalIgnoreCase)) ||
            (element.RefName != null && 
            element.RefName.Name != null &&
            element.RefName.Name.Equals(
                memberName, StringComparison.OrdinalIgnoreCase))))
        {
            return GetCommentsFromElement(element);
        }

        group = item as XmlSchemaGroupRef;

        if (group != null)
        {
            comments = GetCommentsFromGroupRef(group, memberName);
        }

        choice = item as XmlSchemaChoice;

        if (choice != null)
        {
            comments = GetCommentsFromChoice(choice, memberName);
        }

        any = item as XmlSchemaAny;

        if (any != null)
        {
            comments = GetCommentsFromAny(any);
        }

        sequence = item as XmlSchemaSequence;

        if (sequence != null)
        {
            comments = GetCommentsFromSequence(sequence, memberName);
        }

        if (!string.IsNullOrEmpty(comments))
        {
            return comments;
        }
    }

    return null;
}

private static string GetCommentsFromAny(XmlSchemaAny element)
{
    XmlSchemaDocumentation documentation;

    if (element != null && 
        element.Annotation != null && 
        element.Annotation.Items != null && 
        element.Annotation.Items.Count > 0)
    {
        foreach (XmlSchemaObject elementItem in element.Annotation.Items)
        {
            documentation = elementItem as XmlSchemaDocumentation;

            if (documentation != null && documentation.Markup.Length > 0)
            {
                return documentation.Markup[0].Value;
            }
        }
    }

    return null;
}

private static string GetCommentsFromAll(XmlSchemaAll compositor, string memberName)
{
    if (compositor == null || 
        compositor.Items == null || 
        compositor.Items.Count == 0 || 
        string.IsNullOrWhiteSpace(memberName))
    {
        return null;
    }

    XmlSchemaElement element;

    foreach (XmlSchemaObject item in compositor.Items)
    {
        element = item as XmlSchemaElement;

        if (element != null && 
            element.Name.Equals(memberName, StringComparison.OrdinalIgnoreCase))
        {
            return GetCommentsFromElement(element);
        }
    }

    return null;
}

private static string GetCommentsFromGroupRef(
    XmlSchemaGroupRef element, string memberName)
{
    if (element == null || 
        string.IsNullOrWhiteSpace(memberName) ||
        !element.RefName.Name.Equals(memberName, StringComparison.OrdinalIgnoreCase) ||
        element.Annotation == null || 
        element.Annotation.Items == null || 
        element.Annotation.Items.Count == 0)
    {
        return null;
    }

    XmlSchemaDocumentation documentation;

    foreach (XmlSchemaObject elementItem in element.Annotation.Items)
    {
        documentation = elementItem as XmlSchemaDocumentation;

        if (documentation != null && documentation.Markup.Length > 0)
        {
            return documentation.Markup[0].Value;
        }
    }

    return null;
}

private static string GetCommentsFromChoice(
    XmlSchemaChoice compositor, string memberName)
{
    if (compositor == null || 
        compositor.Items == null || 
        compositor.Items.Count == 0 || 
        string.IsNullOrWhiteSpace(memberName))
    {
        return null;
    }

    XmlSchemaElement element;
    XmlSchemaGroupRef group;
    XmlSchemaChoice choice;
    XmlSchemaAny any;
    XmlSchemaSequence sequence;
    string comments = null;

    foreach (XmlSchemaObject item in compositor.Items)
    {
        element = item as XmlSchemaElement;

        if (element != null &&
            ((element.Name != null && 
            element.Name.Equals(memberName, StringComparison.OrdinalIgnoreCase)) ||
            (element.RefName != null && 
            element.RefName.Name != null &&
            element.RefName.Name.EndsWith(
                ":" + memberName, StringComparison.OrdinalIgnoreCase))))
        {
            return GetCommentsFromElement(element);
        }

        group = item as XmlSchemaGroupRef;

        if (group != null)
        {
            comments = GetCommentsFromGroupRef(group, memberName);
        }

        choice = item as XmlSchemaChoice;

        if (choice != null)
        {
            comments = GetCommentsFromChoice(choice, memberName);
        }

        any = item as XmlSchemaAny;

        if (any != null)
        {
            comments = GetCommentsFromAny(any);
        }

        sequence = item as XmlSchemaSequence;

        if (sequence != null)
        {
            comments = GetCommentsFromSequence(sequence, memberName);
        }

        if (!string.IsNullOrEmpty(comments))
        {
            return comments;
        }
    }

    return null;
}

private static string GetCommentsFromSimpleType(
    XmlSchemaSimpleType simple, string memberName)
{
    XmlSchemaDocumentation documentation;

    // Only handling simple types that are enumerations for now.

    XmlSchemaSimpleTypeRestriction restriction = 
        simple.Content as XmlSchemaSimpleTypeRestriction;

    if (restriction != null)
    {
        if (string.IsNullOrWhiteSpace(memberName))
        {
            if (simple.Annotation != null && 
                simple.Annotation.Items != null && 
                simple.Annotation.Items.Count > 0)
            {
                foreach (XmlSchemaObject simpleItem in simple.Annotation.Items)
                {
                    documentation = simpleItem as XmlSchemaDocumentation;

                    if (documentation != null && documentation.Markup.Length > 0)
                    {
                        return documentation.Markup[0].Value;
                    }
                }
            }
        }
        else
        {
            XmlSchemaEnumerationFacet enumeration;

            foreach (XmlSchemaObject facet in restriction.Facets)
            {
                enumeration = facet as XmlSchemaEnumerationFacet;

                if (enumeration != null && 
                    enumeration.Value.Equals(memberName, 
                        StringComparison.OrdinalIgnoreCase) &&
                    enumeration.Annotation != null && 
                    enumeration.Annotation.Items != null &&
                    enumeration.Annotation.Items.Count > 0)
                {
                    foreach (XmlSchemaObject enumItem in enumeration.Annotation.Items)
                    {
                        documentation = enumItem as XmlSchemaDocumentation;

                        if (documentation != null && documentation.Markup.Length > 0)
                        {
                            return documentation.Markup[0].Value;
                        }
                    }
                }
            }
        }
    }

    return null;
}Code language: C# (cs)