My previous utility [^] helped you to create XSD files. After completing this, I hunted around for a utility that would let me manage the data of an associated XML document. Specifically, I wanted two features:

A generic solution that would let me create XML data against any XSD schema

Something that customizes the input control based on schema information

I did not find anything that came close to meeting these two criteria. All of the editors I found were oriented around editing the entire XML document, tags and all, not just the data. For me, the tags are a major encumberance to quickly putting together a data set. Furthermore, the .NET solution, which is to create a CS file, is a complicated, multi-step and non-generic approach.

Thus, I decided to write my own XML data editor and, in the process, learn more about XSD, XML, and XPath. Utilizing the XmlDataDocument, XmlSchema and DataTable classes, I have created a generic editor that dynamically creates specialized controls for data entry, based on the schema definition and table relationships.

An example of the result is: (Sorry about the long picture!)

...which is derived from the schema (screenshot from my XML Schema Editor):

// get a stream reader for the XSD file
StreamReader tr=new StreamReader(dlgOpen.FileName);
// read the file into the XmlSchema object
schema=XmlSchema.Read(tr,
new ValidationEventHandler(SchemaValidationHandler));
tr.Close();
// report any problems with the schema by compiling it
CompileSchema();
// create the document
doc=new XmlDataDocument();
// open the schema again
tr=new StreamReader(dlgOpen.FileName);
// read it into the DataSet member
doc.DataSet.ReadXmlSchema(tr);

Once the schema has been loaded into the DataSet, the .NET framework automatically creates the associated DataTable objects and their relationships. This information is used to generate the framework for the GUI, which is a series of recursive GroupBox objects encapsulating Control objects, all contained within a panel. The panel provides us with automatic scrolling capabilities, which eleviates the task of managing the scroll functions ourselves.

The GUI generation begins by inspecting all root (not parent) tables and determining the XmlSchemaComplexType that defines them. The assumption here is that any root table will be represented in the schema as an element referencing a global complex type.

privatevoid ConstructGUI(DataSet dataSet)
{
Point pos=new Point(10, 10);
// get all tables in the dataset
foreach (DataTable dt in dataSet.Tables)
{
// but we're only interested in the toplevel tables
if (dt.ParentRelations.Count==0)
{
/*
* Rule 1:
* A top level table will be a top level element in the schema that
* is of a complex type. The element name will be the table name.
* What we want to identify is the complex
* type that the table references,
* so that we can determine the data types of the columns.
*
* Any other rules???
*/
XmlSchemaElement el=GetGlobalElement(dt.TableName);
XmlSchemaComplexType ct=GetGlobalComplexType(el.SchemaTypeName.Name);
Point p2=ConstructGUI(pos.X, pos.Y, dt, pnlDynForm, ct);
pos.Y+=p2.Y;
}
}
}

There are two very simple helper functions to acquire the appropriate objects, illustrated here because of the different mechanism required to obtain them:

The GroupBox is created in a straightforward manner, along with a helper hash table called tableInfo. Initially, the group box is not given any size, as this is determined after all the controls have been placed. Also, a navigation bar is created and associated with the group box, providing the first, previous, next, last, new and delete record buttons, as well as the n of m record info text.

2. The placement of the column name and read-only text control for hidden fields. This could be omitted, but I left it in so that I could inspect the hidden features of the DataSet. Columns that have relationships with child tables are colored blue and columns that have a relationship with a parent table are colored red (setting the color is not shown here). Because of the nature of schema, these relationships are always 1:1 (as far as I've seen!).

The controls within the group box are created based on some simplistic rules from information gathered from the schema. This area could be greatly expanded because I simply didn't want to implement all the basic types. The rules are as follows:

If the element contains an enumeration, create a ComboBox using the enumeration data.

If the element contains minInclusive and maxInclusive facets, create a NumericUpDown control.

If the element is a Boolean type, create a CheckBox control.

If the element is a decimal or positive integer type, create a right aligned TextBox control.

These rows are stored in the tableInfo as an array for easy lookup. There are two helper functions that are used extensively. The first adjusts the "record m of n" display for the specified navigator's TextBox. There's a test here to set the position tracker if the table is a root table. This is probably not the best place for this code!

The GetMatchingRows helper is very useful in determining the child rows that match the parent's ID. Given the child table, the code acquires the parent table and the column that establishes the relationship. It is assumed, rightly so I believe, that there will ever only be one column that defines the relationship. From this, the BindingContext is used to locate the current parent row. Given the parent row, we can acquire the value of the ID and execute a "select" query on the child rows matching the relationship ID.

privateint GetMatchingRows(DataTable dt)
{
TableInfo ti=tableInfo[dt] as TableInfo;
// get parent relationship
DataRelation parentRelation=dt.ParentRelations[0];
// get the parent table
DataTable dt2=parentRelation.ParentTable;
// get the parent column (1:1 relationship always)
DataColumn dcParent=parentRelation.ParentColumns[0];
// get the current record # of the parent
int n=BindingContext[dt2].Position;
if (n != -1)
{
// get the ID
string val=dt2.Rows[n][dcParent].ToString();
// search the child for all records where child.parentID=parent.ID
string expr=dcParent.ColumnName+"="+val;
// save the rows, as we'll use them later on when navigating the child
ti.dataRows=dt.Select(expr);
}
// return the length
return ti.dataRows.Length;
}

Each handler -- first, previous, next and last -- has basically the same form. I'll demonstrate the "next" handler. Each button in any given navigation bar is "tagged" with the DataTable on which it operates. From this information, the tableInfo object is accessed in order to update the internal record position. Note that for child records, this position is relative to the rows selected by the parent ID, as opposed to a position relative to all the records in the child table.

For this reason, a little bit of manipulation is necessary to acquire the desired record. If the table is a root table, then there is a 1:1 correlation between our internal position and the rows in the DataTable. However, if it is a child table, then we have to find the row in the DataTable that matches our row in the selectedrows.

Now, when we change to another record in a parent table, all the children need to be updated to display the set of rows that relate to the parent record. This is accomplished recursively. The matching rows of all children is acquired and the child is set to the first record if it exists. Then the same is done for its children.

Adding a record is rather nasty. Records are added to the end of the collection, which helps a bit. Any relational fields to the parent must be set to the parent ID and all child relationships must have a new record created, as well... Recursively, of course.

privatevoid NewRecord(DataTable parent, DataTable child, DataRelation dr)
{
// add the child record
child.Rows.Add(child.NewRow());
// get the last row of the parent (this is the new row)
// and the new row in the child (also the last row)
int newParentRow=parent.Rows.Count-1;
int newChildRow=child.Rows.Count-1;
// go to this record
BindingContext[child].Position=newChildRow;
// get the parent and child columns
// copy the parent ID (auto sequencing) to the child to establish
// the relationship. This is always a 1:1 relationship
DataColumn dcParent=dr.ParentColumns[0];
DataColumn dcChild=dr.ChildColumns[0];
string val=parent.Rows[newParentRow][dcParent].ToString();
child.Rows[newChildRow][dcChild]=val;
((TableInfo)tableInfo[child]).pos=1;
((TableInfo)tableInfo[child]).rows=1;
UpdateRecordCountInfo(child);
// recurse into children of this child
foreach (DataRelation childRelation in child.ChildRelations)
{
DataTable dt2=childRelation.ChildTable;
NewRecord(child, dt2, childRelation);
}
}

Data binding makes life incredibly easy with regards to associating a control with a column in a DataTable, and is accomplished with one line:

ctrl.DataBindings.Add("Text", dt, dc.ColumnName);

This binds the "Text" field of the control to the specified data table and column. Isn't reflection great? The BindingContext is another life-saver. It allows us to specify the row in each DataTable that is used to bind the controls. Awesome! For example, given the table, we can specify the binding row with one line:

I implemented XPath capabilities so that I could play with how to extract information from the XML data. Coming from a database background, I wanted something that would flatten the hierarchy so that I could see all the data at once, without needing to mouse-click, etc. through layers of hierarchy. Using the SelectNodes method of the XmlDataDocument:

I extract a node list and, if it's successful, pass it on to a dialog box. The data is displayed in a ListView control with headers extracted from XmlNode. There are three basic steps in displaying node data:

The header information is extracted from both the attributes and the elements of the node list. This function operates recursively on elements and therefore can generate unwanted columns. Also, duplicate element names are ignored, which can lead to overwrite problems if your schema uses the same element name in two different areas and you happen to query on those elements.

After writing all this, I finally feel like I have a set of tools that I can use to generate schemas and manipulate the XML data. While not addressing all the issues of schemas, facets, etc. (the "choice" schema element looks particularly nasty), I feel that I've created a good set of tools for accomplishing 90% for which I need schemas and XML files. If there's any particular feature you feel is a "must have," let me know and I'll try to encorporate it.

Excellent piece of work. I trialled it for use by business users on my client site but the message was just too complex (I can supply an XSD link offline).

It's a customs message similar to that used the world over for SAD's with 105 fields split between header and item level. The item level can repeat up to 1000 times and each item has package and routing data which may also repeat within the item.

This is what I found:

It appeared to load the sample perfectly. The autogenrated form was as expected. This copy of the message only had one instance of any field.

So starting at the top of the message I keyed in amended values beginning with 1 and incrementing by 1 up to 104 (leaving field 105 unchanged) then saved the resulting message to a new name.

At a glance it looked like a good output message, but looks deceived:

Expected address Values 9 thru 12 now have values 74 thru 77, we jump from 17 to 36 and there are 28 fields where the amendment did not get recorded (in addition to changes recorded incorrectly).

Well, without seeing the XSD, I have no idea. Plus, given that this code is 10 years old, there's a lot of changes that have happened in .NET that I would seriously consider reworking the whole concept to take advantage of newer .NET API's.

Hi Do anyone have contact Information of Marc Clifton i already sent him mails to his marc.clifton@gmail.com but unfortunatly no replies so was just wondering is he using still this id? if not can anyone provide me latest contact info

GetLocalElement() method that seems to have addressed an apparent problem the code was having when dealing with complexTypes that were extensions of a base complexType.... For some reason the code wasn't seeing element members of the derived complexType that didn't originally belong to the base.

i seen many of them already described this problem in the post so please if u did any modifications to code can u please let me know iam still facing a prob in reading an XSD file ( i could able to read couple of XSD files but not others)

Hai!
This is Kumar!
After finished my engg degree, now i am working in a s/w company!
And my first work there is to Generate EDI streams(837P) as per HIPAA low using XSD
(I had rule file sheet for 837P) and XML. first i need to insert my DB values into a xml file after validating with the values against rule file! After finishing this, based on that xml file that i have to generate an stream!
I fell that, your concept is something like that! can you please give me some ideas to do this! This is my first work, so i have to do more carefully!