(c) 2005 Marc Clifton All Rights Reserved.

Contents
This article presents an editor for XML Schema Definition (XSD) documents,
implemented in C# using .NET's XmlSchema classes. Searching the Internet, I
found only one such editor: XML
Architect[^]. I
found that the editor included in VS.NET to be unecessarily similar to a
database schema editor. My personal opinion is that XML is more hierarchical and
less relational than a database schema, and so I feel that a tree view is a
better presentation of the schema.
The editor has been tested by using it to
create the Purchase Order XSD that is used in the XML Schema Primer[^].
- dynamically adjusts for global and local types
- dynamically creates complex types from elements
- selecting a tree node highlights the corresponding XML text
- compiler error window
- automatically selects "ref" or "type" for elements and attributes
- xml can be edited directly instead of using the tree
- creates and manages global types list as types are added and removed
- keyboard shortcuts:
- F2-edit node label
- Ctrl-A : add to schema at current node
- Ctrl-T : go to top of schema
- Ctrl-P : got to parent of current node
The SOM is a complex and
unwieldy beast that implements a set of classes that corresponds to the World
Wide Web Consortium (W3C[^]) XML Schema Recommendation. The
XSD Editor implements the following schema types using the SOM (images taken
from the MSDN SOM hierarchy document):
XmlSchemaFacet and XmlSchemaNumericFacet classes
XmlSchemaType classes
XmlSchemaObject classes, except for XmlSchemaAnnotated
XmlSchemaSimpleTypeRestriction within the XmlSchemaSimpleTypeContent
class

XmlSchemaAttribute class
These classes provide the basic functionality for designing an XSD document.
There are many other classes in the SOM that this editor does not currently
support. I will be adding support for these additional classes as required.
Usage is quite straightforward.
With a blank schema,
either load in a schema using the File/Open command.
Add and remove nodes to
the schema tree by right clicking on a tree node. A popup menu provides the
different schema element that can be added. Note that this menu is not context
sensitive to the allowable schema types for the selected node. Any schema
element that is added defaults to the "xs:string" type.
The editor
automatically tracks global schema elements. To change the type of a schema
element, select the desired element in the schema tree and then select the
desired type, either from the simple types combo-box or the global types
combo-box. If it is appropriate to use a reference, the editor will
automatically make the necessary changes to do so, or vice-versa, if an element
should be a type.
The
editor will automatically highlight the line in the schema corresponding to the
node in the tree. If this is not working correctly, select Schema/Compile from
the menu to synchronize the schema with the tree.
The schema can be edited directly in the schema edit box.
Remember to compile the schema after making changes. If you fail to do
this, your changes will be lost if you then manipulate the schema using the
tree.
I found that using .NET's SOM is not
trivial. It is not a generic hierarchical class model. Schema elements are
contained within collections of different members of a schema object. For
example, an XmlSchemaComplexType attributes are contained in the
Attribute member, while the sub-elements are contained in an
XmlSchemaSequence object which is an object assigned to the
Particle member. Furthermore, given a schema object, it is
impossible to determine the parent schema element. This all makes for some
complicated rules for inserting and removing schema objects.
The Remove function in the editor illustrates this:
// The parent type determines from what list the selected item must be removed.
// Use the image index in the tree view to figure out the parent type.
private void mnuRemoveNode_Click(object sender, System.EventArgs e)
{
TreeNode tnParent=tvSchema.SelectedNode.Parent;
XmlSchemaObject obj=tvSchema.SelectedNode.Tag as XmlSchemaObject;
bool success=false;
// if the node to remove has a parent and is of an XmlSchemaObject type...
if ( (tnParent != null) && (obj != null) )
{
// look at the tree node image index to figure out what the parent is!
Note that I get the parent from the tree view, not the schema!
And I use the tree view's image to determine the parent type!
Schema root objects are simply removed:
switch ((TreeViewImages)tnParent.ImageIndex)
{
// if the parent is the schema root:
case TreeViewImages.Schema:
{
// remove the object from the schema and from the global list
schema.Items.Remove(obj);
int idx=cbGlobalTypes.FindStringExact(tvSchema.SelectedNode.Text);
if (idx != -1)
{
cbGlobalTypes.Items.RemoveAt(idx);
}
success=true;
break;
}
Schema annotation objects removed from the
XmlSchemaAnnotation Items member: // if the parent is an annotation type
case TreeViewImages.Annotation:
{
XmlSchemaAnnotation annot=tnParent.Tag as XmlSchemaAnnotation;
if (annot != null)
{
annot.Items.Remove(obj);
success=true;
}
break;
}
If the parent is an XmlSchemaSimpleType, then I have to
remove the object from different places, depending on the object
type--XmlSchemaAnnotation or XmlSchemaFacet: // if the parent is a simple type
case TreeViewImages.SimpleType:
{
// a simple type can have an annotation or a facet type as children
XmlSchemaSimpleType st=tnParent.Tag as XmlSchemaSimpleType;
if (obj is XmlSchemaAnnotation)
{
// remove from annotation list if it's an annotation type
st.Annotation.Items.Remove(obj);
success=true;
}
else if (obj is XmlSchemaFacet)
{
XmlSchemaSimpleTypeRestriction rest=st.Content as XmlSchemaSimpleTypeRestriction;
if (rest != null)
{
// remove from facet list if it's a facet type
rest.Facets.Remove(obj);
success=true;
}
}
break;
}
A parent cannot be an XmlSchemaElement, because any element
with sub-elements is an XmlSchemaComplexType, which can contain an
XmlSchemaAttribute or XmlSchemaElement objects as part
of an XmlSchemaSequence Items collection. // if the parent is a complex type...
case TreeViewImages.ComplexType:
{
XmlSchemaComplexType ct=tnParent.Tag as XmlSchemaComplexType;
if (ct != null)
{
// then we are removing an attribute
if (obj is XmlSchemaAttribute)
{
ct.Attributes.Remove(obj);
success=true;
}
// or an annotation
else if (obj is XmlSchemaAnnotation)
{
ct.Annotation.Items.Remove(obj);
success=true;
}
// or an element type
else
{
XmlSchemaSequence seq=ct.Particle as XmlSchemaSequence;
if (seq != null)
{
seq.Items.Remove(obj);
success=true;
}
}
}
break;
}
}
}
...
}
As you can see, this is not trivial.
1. Clicking on node A while in the middle of
editing node B results in the name being changed for node A (mouse down event
handler sets the current node!)
2. After editing the schema in the text edit
box, you MUST compile otherwise changes will be lost!
3. If the tree node
doesn't match the schema object, there are blank lines in the XSD file which are
causing synchronization problems. Select "Schema/Compile" from menu.
1. Auto-scroll edit
box to show selected node
2. Remember selected node when compiling
schema
3. Implement XmlSchemaElement minOccurs and maxOccurs with GUI to
enter values
4. Implement XmlSchemaAttribute fixed and use with GUI to enter
values
5. Implement XmlSchemaGroup
6. others?
I found that writing this editor was
an excellent way to understand the nuts and bolts of XSD documents. For the
curious, this editor represents about 40 hours of work!