[ Team LiB ] |
8.3 Working with SchemasBecause the XML Schema language is expressed as an XML vocabulary, it can be dealt with just as any other XML document. XmlReader, XmlWriter, XmlDocument, XPathNavigator, XsltTransform, and the rest of .NET's types can be used to read, write, manipulate, navigate, and transform an XSD document. In addition to the standard ways of dealing with XML documents, however, the System.Xml.XmlSchema assembly includes a host of types that can be used to deal with schema documents in a very XSD-centric way. 8.3.1 Creating a Schema ProgrammaticallyAs you already know, xs:schema is the root element of an XSD document. XmlSchema is the type that represents the xs:schema element. XmlSchema is a subclass of XmlSchemaObject, whose other subclasses are XmlSchemaAnnotated, XmlSchemaAnnotation, XmlSchemaAppInfo, XmlSchemaDocumentation, and XmlSchemaExternal. Each of these subclasses represents a specific type of XML Schema element, and some of them have their own subclasses. The .NET XmlSchema type hierarchy is shown in Figure 8-1. Figure 8-1. XmlSchema type hierarchyTable 8-4 shows each XML Schema element name with its corresponding .NET type. In some cases, more than one .NET class is used for the same XML Schema element; typically, this is the case when the same element has different behavior in different contexts. There are many more types in the System.Xml.Schema assembly that do not correspond directly to an XML Schema element, and they are listed in the assembly reference in Chapter 17.
You can access all the attributes of each XSD element directly through the corresponding .NET type's properties; each property is named the same as the corresponding attribute, with an initial capital letter. In those cases where an attribute is not valid because a particular element can appear in different contexts, an exception is thrown if you try to access the corresponding property. Creating an instance of an XSD programmatically, then, is a fairly simple process, illustrated in Example 8-7. The overall process should be reminiscent of creating a DOM instance with XmlDocument. Example 8-7. Creating an XSD instance programmaticallyusing System; using System.IO; using System.Xml; using System.Xml.Schema; public class CreateSchema { public static void Main(string [ ] args) { string ns = "http://www.w3.org/2001/XMLSchema"; XmlQualifiedName idType = new XmlQualifiedName("ID",ns); XmlQualifiedName stringType = new XmlQualifiedName("string",ns); XmlQualifiedName tokenType = new XmlQualifiedName("token",ns); XmlSchema schema = new XmlSchema( ); schema.Version = "1.0"; XmlSchemaElement customer = new XmlSchemaElement( ); customer.Name = "Customer"; schema.Items.Add(customer); XmlSchemaComplexType customerComplexType = new XmlSchemaComplexType( ); customer.SchemaType = customerComplexType; XmlSchemaSequence customerSequence = new XmlSchemaSequence( ); customerComplexType.Particle = customerSequence; XmlSchemaElement name = new XmlSchemaElement( ); name.Name = "Name"; name.MinOccurs = 1; name.MaxOccurs = 1; name.SchemaTypeName = tokenType; customerSequence.Items.Add(name); XmlSchemaElement address = new XmlSchemaElement( ); address.Name = "Address"; address.MinOccurs = 1; address.MaxOccursString = "unbounded"; customerSequence.Items.Add(address); XmlSchemaComplexType addressComplexType = new XmlSchemaComplexType( ); address.SchemaType = addressComplexType; XmlSchemaSequence addressSequence = new XmlSchemaSequence( ); addressComplexType.Particle = addressSequence; XmlSchemaElement street = new XmlSchemaElement( ); street.Name = "Street"; street.MinOccurs = 1; street.MaxOccurs = 3; street.SchemaTypeName = stringType; addressSequence.Items.Add(street); XmlSchemaElement city = new XmlSchemaElement( ); city.Name = "City"; city.MinOccurs = 1; city.MaxOccurs = 1; city.SchemaTypeName = stringType; addressSequence.Items.Add(city); XmlSchemaElement state = new XmlSchemaElement( ); state.Name = "State"; state.MinOccurs = 1; state.MaxOccurs = 1; state.SchemaTypeName = stringType; addressSequence.Items.Add(state); XmlSchemaElement zip = new XmlSchemaElement( ); zip.Name = "Zip"; zip.MinOccurs = 1; zip.MaxOccurs = 1; zip.SchemaTypeName = new XmlQualifiedName("USZipCodeType"); addressSequence.Items.Add(zip); XmlSchemaAttribute customerId = new XmlSchemaAttribute( ); customerId.Name = "Id"; customerId.SchemaTypeName = idType; customerComplexType.Attributes.Add(customerId); XmlSchemaSimpleType usZipCodeType = new XmlSchemaSimpleType( ); usZipCodeType.Name = "USZipCodeType"; schema.Items.Add(usZipCodeType); XmlSchemaSimpleTypeRestriction zipRestriction = new XmlSchemaSimpleTypeRestriction( ); zipRestriction.BaseTypeName = tokenType; usZipCodeType.Content = zipRestriction; XmlSchemaPatternFacet zipPattern = new XmlSchemaPatternFacet( ); zipPattern.Value = @"\d{5}(-\d{4})?"; zipRestriction.Facets.Add(zipPattern); schema.Compile(new ValidationEventHandler(Handler)); if (schema.IsCompiled) { Console.WriteLine("Schema compiled."); schema.Write(File.Create("Customer.xsd")); } else { Console.WriteLine("Schema compilation failed."); } } public static void Handler(object sender, ValidationEventArgs e) { Console.WriteLine(e.Message); } } Running this code produces an XSD equivalent to the Customer.xsd that started the chapter (there are minor differences, such as the order of attributes, but the canonical XML is the same). Most of this code should be self-explanatory. The general process takes the following steps:
A few particular sections of code do bear further explanation: XmlQualifiedName idType = new XmlQualifiedName("ID",ns); XmlQualifiedName stringType = new XmlQualifiedName("string",ns); XmlQualifiedName tokenType = new XmlQualifiedName("token",ns); These lines create instances of the type XmlQualifiedName. XmlQualifedName represents a qualified name in XML, in the format namespace:localname. The document's XmlNameTable is used to resolved the namespace URI in the second parameter to the appropriate prefix; in this case, that's xs. You'll use these XmlQualifiedName instances later, when setting the type attributes of xs:element and xs:attribute elements: zip.SchemaTypeName = new XmlQualifiedName("USZipCodeType"); This line sets the Zip element's type attribute to USZipCodeType. This simple type hasn't actually been defined yet at this point, but that's ok. The document need not be valid until it is compiled; in fact, an invalid XSD may be written to disk without any error being raised. That's why the last few lines of the program are included: if (schema.IsCompiled) { Console.WriteLine("Schema compiled."); schema.Write(File.Create("Customer.xsd")); } else { Console.WriteLine("Schema compilation failed."); } These lines make sure that the XSD is valid before writing it to disk. Although the ValidationEventHandler would have written a message to the console if there were any problems, the IsCompiled property of the XmlSchema instance can be queried as a final check before writing the XSD to disk. The entire process is fairly straightforward, and should need no further explanation. 8.3.2 Manipulating an Existing SchemaYou already know how to load any XML document into memory. Once you have a document in memory, you can navigate through its elements using XmlDocument's standard methods, and read it into an XmlSchema for other purposes: XmlDocument document = new XmlDocument( ); document.Load(args[0]); XmlNodeReader reader = new XmlNodeReader(document); ValidationEventHandler handler = new ValidationEventHandler(Handler); XmlSchema schema = XmlSchema.Read(reader, handler); schema.Compile(handler); In much the same way, you can use XmlDocument's GetNavigator( ), SelectNodes( ), and SelectSingleNode( ) methods to navigate an XSD. You can also transform it into any other format, given an appropriate XSLT stylesheet. For example, you might wish to transform an XSD into a DTD; you could write an XSLT transform to do so. |
[ Team LiB ] |