Sign In/My Account | View Cart  
advertisement


Listen Print Discuss

The .NET Schema Object Model
by Priya Lakshminarayanan | Pages: 1, 2, 3

Including or Importing Schema Documents

A schema may contain import, include, and redefine elements. These point to other schema documents and augment the structure of the main schema in some way.

For example, the address.xsd sample schema in the WXS Primer can be incorporated into our customer schema so that different address types are available for use. We can incorporate it either as an <xs:include> or <xs:import>, to use its components as-is, or as <xs:redefine> to modify any of its components to suit our needs. Since the address schema has a targetNamespace that is different from that of our customer schema, we use the import semantics.

The XmlSchema object has an Includes property that can be accessed to get at all the includes, imports, or redefines added to the main schema. Each component added to this collection is of type XmlSchemaExternal. Since the XmlSchema object also exposes an Items collection, one might incorrectly assume that the includes and imports defined in the schema are added to it. In fact, they are added to a separate collection which can accessed through the Includes property on XmlSchema. In our example, the imported address schema will be added to the Includes collection and not to the Items collection in the XmlSchema object for the customer schema. The following sample shows how to import the address.xsd schema to the Customer schema:

private void IncludeSchema(string fileName) {

    // Read address.xsd and customer.xsd into the SOM and compile
    XmlSchema addrSchema = ReadAndCompileSchema("address.xsd");
    XmlSchema custSchema = ReadAndCompileSchema("Customer.xsd");

    //Create the import
    XmlSchemaImport imp = new XmlSchemaImport();
    imp.Namespace = "http://www.example.com/IPO";
    imp.Schema = addrSchema;

    //Add the import to the customer schema
    custSchema.Includes.Add(imp);
    custSchema.Compile(new 
       ValidationEventHandler(ValidationCallbackOne));
    custSchema.Write(console.Out);
}

We can also set the SchemaLocation property on the XmlSchemaImport object, instead of the Schema property. In this case, during compilation, the schema will be fetched from the location provided.

The following schema is generated from the sample code:

<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:tns="http://tempuri.org" 
           targetNamespace="http://tempuri.org"
           xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:import namespace="http://www.example.com/IPO"/>
  <xs:element name="Customer">
    <xs:complexType>
      <xs:sequence>
        <xs:element name="FirstName" type="tns:FirstNameCT" />
        <xs:element name="LastName" type="tns:LastNameType" />
        <xs:element name="PhoneNumber">
          <xs:simpleType>
            <xs:restriction base="xs:string">
              <xs:pattern value="\d{3}-\d{3}-\d{4}" />
            </xs:restriction>
          </xs:simpleType>
        </xs:element>
      </xs:sequence>
      <xs:attribute name="CustID" type="xs:positiveInteger"
                    use="required" />
    </xs:complexType>
  </xs:element>
  <xs:simpleType name="LastNameType">
    <xs:restriction base="xs:string">
      <xs:maxLength value="20"/>
    </xs:restriction>
  </xs:simpleType>
  <xs:complexType name="FirstNameCT">
    <xs:simpleContent>
      <xs:extension base="xs:string">
        <xs:attribute name="Title" type="xs:string" />
      </xs:extension>
    </xs:simpleContent>
  </xs:complexType>
</xs:schema>

If we have included or imported a number of related schemas to the Customer schema -- Order.xsd, address.xsd, orderdetail.xsd and so on -- the following code demonstrates recursive iteration through them and each of their includes or imports:

private void TraverseExternals() {
    XmlSchema custSchema = ReadAndCompileSchema("Customer.xsd");
    RecurseExternals(custSchema);
}

private void RecurseExternals(XmlSchema schema) {
    foreach(XmlSchemaExternal ext in schema.Includes) {
        if(ext.SchemaLocation != null) {
            Console.WriteLine("External SchemaLocation: " +
                ext.SchemaLocation);
        }
        if (ext is XmlSchemaImport) {
            XmlSchemaImport imp = (XmlSchemaImport)ext;
            Console.WriteLine("Imported Namespace: " + 
                imp.Namespace);
        }

        if (ext.Schema != null) {
            ext.Schema.Write(Console.Out);
            //Traverse its externals
            RecurseExternals(ext.Schema);
        }
    }
}

A Schema Library -- The XmlSchemaCollection

For most applications that make use of schemas, there is a need to be able to load a set of schemas together for validating instance documents and cache the schemas for later reuse. The XmlSchemaCollection is designed to serve this purpose. The XmlSchemaCollection can store a set of WXS schemas or XML-Data Reduced (XDR) schemas.

The following code sample illustrates the usage of this class:

private static XmlSchemaCollection CreateSchemaCollection() {
    XmlSchemaCollection xsc = new XmlSchemaCollection();
    xsc.ValidationEventHandler += 
        new ValidationEventHandler(ValidationCallbackOne);
    xsc.Add(null,"address.xsd");
    xsc.Add(null,"Customer.xsd");
    return xsc;
}

The Add method loads the schema document into an XmlSchema object, then compiles the schema and adds it to the collection. The first parameter takes the namespace that the schema belongs to, which is the same as the targetNamespace of the schema. If this parameter is null, the targetNamespace defaults to the one defined in the schema.

Validation using the XmlSchemaCollection

The XmlValidatingReader class in the .NET Framework is used for validating XML documents against a schema or a set of schemas. The XmlSchemaCollection is used by the validating reader for efficient validation. Schemas can be added to the schema collection by accessing its Schemas property. We can either add individual schemas or an entire collection. The following code validates the Customer.xml file against the Customer schema.

private static void Validate() {
    XmlTextReader tr = new XmlTextReader("Customer.xml", 
                                         new NameTable());
    XmlValidatingReader vr = new XmlValidatingReader(tr);
    vr.ValidationEventHandler += 
        new ValidationEventHandler(ValidationCallbackOne);
    vr.ValidationType = ValidationType.Schema;
    vr.Schemas.Add(CreateSchemaCollection());
    while(vr.Read()) {} //Validate
}

Security in the SOM -- Can You Trust Your Schema?

One reason WXS schemas are important to XML is because they set the ground rules for communication across different XML applications. When schemas have to be exchanged across applications, situations may arise in which you cannot trust the source from which your schemas originated. There was a recent question asked on one of our discussion lists about how security is handled in the SOM:

"The larger question I’m investigating is that my app reads XSD schemas, and I’d like to attempt to find externally referenced schemas for the user. I’m trying to figure out if I can safely open imports or includes that have a schemaLocation value without allowing a malicious schema to call a harmful URL with my user’s credentials (like schemaLocation="http://my401k.com/sellEverything?sendCheckTo=MyAddress").

The SOM provides a way by which one can choose to not resolve the externals if the schema is not from a trustworthy source. The Compile method on the XmlSchema class has an overload that takes in an XmlResolver. The Add() methods on XmlSchemaCollection also have corresponding overloads that take in XmlResolvers. In both cases, you can pass in null for the resolver so that the schemaLocation of the includes, imports, and redefines are not resolved. This is available in the Beta 1 release of the .Net Framework. You can also create your own custom resolvers by subclassing the XmlResolver class and overriding the GetEntity method.

Further Reading

The following are links to the complete reference on the Schema Object Model, the class hierarchy in the System.Xml.Schema namespace in the .NET Framework class library.

Acknowledgments

Thanks to Dare Obasanjo, Mark Fussell, Tejal Joshi and Yan Leshinsky for reviewing this article and providing feedback.


Comment on this articleShare your comments or questions on this article in our forum
(* You must be a
member of XML.com to use this feature.)
Comment on this Article


Titles Only Titles Only Newest First
  • how to traverse XML Schemas
    2006-05-02 21:26:37 Forum [Reply]

    Hi priya


    this is my xsd file


    <?xml version="1.0" encoding="utf-8"?>
    <xs:schema targetNamespace="http://tempuri.org/XMLSchema.xsd" elementFormDefault="qualified" xmlns="http://tempuri.org/XMLSchema.xsd" xmlns:mstns="http://tempuri.org/XMLSchema.xsd" xmlns:xs="http://www.w3.org/2001/XMLSchema">
    <!-- -->
    <xs:element name="Plan">
    <xs:complexType>
    <xs:sequence>
    <xs:element name="Patient_Data">
    <xs:complexType>
    <xs:sequence>
    <xs:element name="Name_of_Patient" type="xs:string" />
    <xs:element name="Patient_ID" type="xs:string" />
    <xs:element name="Clinical_Record" type="xs:string" />
    <xs:element name="Site_of_Target_Vol" type="xs:string" />
    </xs:sequence>
    </xs:complexType>
    </xs:element>
    <xs:element name="General_Data">
    <xs:complexType>
    <xs:sequence>
    <xs:element name="Planned_on" type="xs:date" />
    <xs:element name="Planned_by" type="xs:string" />
    <xs:element name="Date_of_Protocol" type="xs:dateTime" />
    <xs:element name="Number_of_Pages_Protocol" type="xs:int" />
    <xs:element name="Image_Series_ID" type="xs:int" />
    <xs:element name="Target_Point_Mode" type="xs:string" />
    <xs:element name="Device_Coord_Syst" type="xs:string" />
    </xs:sequence>
    </xs:complexType>
    </xs:element>
    </xs:sequence>
    </xs:complexType>
    </xs:element>
    </xs:schema>


    i want to display or print all the elements in the schema., the elements are like "plan" under this "Patient_data and General_data" and under patient_data it has some sub elements. How can i traverse all the elemetns in the file. I tried like this


    XmlTextReader reader = new XmlTextReader(@"E:\XmlTest\Test\PatientData.xsd");
    try
    {
    XmlSchema schema = XmlSchema.Read(reader, null);
    schema.Compile(null);
    if (schema.IsCompiled)
    {
    MessageBox.Show("Inside");
    foreach (XmlSchemaElement parentElement in schema.Elements.Values)
    {
    MessageBox.Show(parentElement.Name.ToString());
    XmlSchemaComplexType ct = parentElement.ElementType as XmlSchemaComplexType; //Casting to complex type
    if (ct != null)
    {
    XmlSchemaSequence seq = (XmlSchemaSequence)ct.ContentTypeParticle;
    foreach (XmlSchemaElement elem in seq.Items)
    {
    MessageBox.Show(elem.Name.ToString());
    }
    }
    }
    it displays only plan and patient_data and General_data it is not displaying sub elements. Can u please tell me where the problem is? share ur ideas


    Thanks in advance


    Regards


    • how to traverse XML Schemas
      2006-05-04 20:09:13 Priyamvadha Lakshminarayanan [Reply]

      In order to get all the elements at all levels, take a look at the code sample in the following blog:
      http://blogs.msdn.com/stan_kitsis/archive/2005/08/06/448572.aspx


      Thanks,
      Priya

  • Traversing the Schema Child with Group Reference
    2003-08-01 08:46:44 Matt Frame [Reply]

    Thank you for getting me started with the SOM. Without this article I would be even more lost than I am now.


    I am trying to traverse down my schema and I do so succesfully for all child elements. The problem is that I also want to get information about the groups that I enter into before the elements. It looks like I can only traverse the element path and that group reference levels are not exposed. Is there a way I can get the group information while traversing the elements?


    Thanks,
    Matt

    • Traversing the Schema Child with Group Reference
      2003-08-01 11:51:14 Priyamvadha Lakshminarayanan [Reply]

      Group references are resolved during compilation. If you want access to them, you should use complexType.Particle property which is the pre-compiled property instead of complexType.ContentTypeParticle which is a post-compilation property.


      For the following schema,
      <xsd:schema attributeFormDefault="qualified" targetNamespace="ns1" xmlns:x="ns1" xmlns:xsd="http://www.w3.org/2001/XMLSchema">


      <xsd:group name="g1">
      <xsd:sequence>
      <xsd:element name="GE1" type="xsd:string"/>
      </xsd:sequence>
      </xsd:group>
      <xsd:complexType name="CT">
      <xsd:sequence>
      <xsd:element name="E1" type="xsd:int"/>
      <xsd:group ref="x:g1"/>
      <xsd:element name="E2" type="xsd:string"/>
      </xsd:sequence>
      </xsd:complexType>
      </xsd:schema>


      If you iterate over the schema.SchemaTypes
      and get the complex type CT,
      complexType.Particle will be a XmlSchemaSequence with 3 items:
      System.Xml.Schema.XmlSchemaElement (E1)
      System.Xml.Schema.XmlSchemaGroupRef (G1)
      System.Xml.Schema.XmlSchemaElement (E2)


      complexType.ContentTypeParticle will be a XmlSchemaSequence with 3 items (the group ref and pointless sequence has been resolved):
      System.Xml.Schema.XmlSchemaElement (E1)
      System.Xml.Schema.XmlSchemaElement (GE1)
      System.Xml.Schema.XmlSchemaElement (E2)


      HTH,
      Priya

      • Traversing the Schema Child with Group Reference
        2003-08-01 14:02:57 Matt Frame [Reply]

        Priya,


        Sorry I forgot to append the test schema I am trying to use now with the same problem.


        <?xml version="1.0" encoding="UTF-8"?>
        <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" attributeFormDefault="unqualified">
        <xs:complexType name="TopLevel">
        <xs:sequence>
        <xs:element name="Elm1"/>
        <xs:element name="Elm2"/>
        <xs:group ref="GroupOne"/>
        </xs:sequence>
        </xs:complexType>
        <xs:group name="GroupOne">
        <xs:sequence>
        <xs:element name="GO_Elm1"/>
        <xs:element name="GO_Elm2"/>
        <xs:group ref="SubGroupOne"/>
        </xs:sequence>
        </xs:group>
        <xs:group name="SubGroupOne">
        <xs:sequence>
        <xs:element name="SGO_Elm1"/>
        <xs:element name="SGO_Elm2"/>
        </xs:sequence>
        </xs:group>
        </xs:schema>


        As you can see the first group has a reference to a another group, here SubGroupOne, this is the level I am trying to get an cannot. I cannot find the SubGroupOne name or any information about it when traversing the tree.


        Thanks,


        Matt

        • Traversing the Schema Child with Group Reference
          2003-08-01 15:57:36 Priyamvadha Lakshminarayanan [Reply]

          XmlSchemaGroupRef does not have the pre-compiled particle on it. Hence you need to look up the group from the RefName and then drill down further:
          The following output is generated by the code sample below for the schema you posted:


          Seq, All or Choice
          XmlSchemaElement: Elm1
          XmlSchemaElement: Elm2
          Group ref
          Seq, All or Choice
          XmlSchemaElement: GO_Elm1
          XmlSchemaElement: GO_Elm2
          Group ref
          Seq, All or Choice
          XmlSchemaElement: SGO_Elm1
          XmlSchemaElement: SGO_Elm2


          Notice the two groupRefs printed.


          private void PrintParticles(string url) {
          XmlSchema schema = XmlSchema.Read(new XmlTextReader(url, new NameTable()), new ValidationEventHandler(ValidationCallback));
          schema.Compile(new ValidationEventHandler(ValidationCallback));
          foreach(XmlSchemaType type in schema.SchemaTypes.Values) {
          XmlSchemaComplexType complexType = type as XmlSchemaComplexType;
          if (complexType != null) {
          TraverseParticle(complexType.Particle, schema);
          }
          }

          foreach(XmlSchemaElement elem in schema.Elements) {
          XmlSchemaComplexType complexType = elem.ElementType as XmlSchemaComplexType;
          if (complexType != null && complexType.QualifiedName == XmlQualifiedName.Empty) { //Only local types of an element
          TraverseParticle(complexType.Particle, schema);
          }
          }
          }

          private void TraverseParticle(XmlSchemaParticle particle, XmlSchema schema) {
          if (particle is XmlSchemaElement) {
          XmlSchemaElement elem = particle as XmlSchemaElement;
          Console.WriteLine("XmlSchemaElement: " + elem.QualifiedName.ToString());
          }
          else if (particle is XmlSchemaGroupRef) {
          XmlSchemaGroupRef groupRef = particle as XmlSchemaGroupRef;
          Console.WriteLine("Group ref");
          XmlSchemaGroup group = (XmlSchemaGroup)schema.Groups[groupRef.RefName]; //Get the group
          TraverseParticle(group.Particle, schema);
          }
          else if (particle is XmlSchemaGroupBase) {
          Console.WriteLine("Seq, All or Choice");
          XmlSchemaGroupBase baseParticle = particle as XmlSchemaGroupBase;
          foreach(XmlSchemaParticle subParticle in baseParticle.Items) {
          TraverseParticle(subParticle, schema);
          }
          }
          }

          • Traversing the Schema Child with Group Reference
            2003-08-02 22:36:39 Matt Frame [Reply]

            Priya,


            Thank you so much for helping me out with this. I am getting a much better understanding of schema's and the SOM because of your help. I have one last question though.


            Is it possible to be in SubGroupOne and find out which parent you came from? I guess I am asking can you traverse the tree in reverse if need be.


            Thanks,


            Matt

      • Traversing the Schema Child with Group Reference
        2003-08-01 13:48:54 Matt Frame [Reply]

        Priya,


        Thanks so much for getting back with me. I have to admit I am new to XML Schemas and the SOM. I see a big difference in the way you created a schema and the way I had, of course I am using XMLSPy and I am new at this. Here is a sample of what I had put together.


        <?xml version="1.0" encoding="UTF-8"?>
        <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" attributeFormDefault="unqualified">
        <xs:element name="TopLevel">
        <xs:annotation>
        <xs:documentation>Test Schema for SOM Development</xs:documentation>
        </xs:annotation>
        <xs:complexType>
        <xs:group ref="GroupOne"/>
        </xs:complexType>
        </xs:element>
        <xs:group name="GroupOne">
        <xs:sequence>
        <xs:element name="GO_Elm1"/>
        <xs:group ref="SubGroupOne"/>
        </xs:sequence>
        </xs:group>
        <xs:group name="SubGroupOne">
        <xs:sequence>
        <xs:element name="SGO_Elm1"/>
        <xs:element name="SGO_Elm2"/>
        </xs:sequence>
        </xs:group>
        </xs:schema>


        This schema doesn't have any schema types so I guess that is why I was having such a hard time traversing the tree, although I was using the Particle and not the ContentParticle.


        I am still having difficulty putting the correct code together to traverse the tree using SchemaTypes. If you have a sample that would be great if you don't that's fine to. I just hate that I have spent a week traversing the tree one way only to find that was completely wrong and now I have to learn how to do it differently.


        I really appreciate your help.


        Thanks,


        Matt

  • Targeting a nested child element
    2003-02-24 10:21:26 Wendy Attenberger [Reply]

    We're trying to follow your example on page 2 of adding an element. We're doing this in memory - adding some complex types, compiling, then trying to go back and access those elements to add the necessary children. We don't want to save to disk until we've added all the children of the children, etc.


    In using the loop -
    For Each parentElement In schema.Elements.Values
    Next
    -
    we can only get to the root element.


    Sample portion of our dynamic schema is included below - only element we can get to is Orders...can't get to the child Order element or the child Customer element. We did a print out of the schema.Elements.Values.Count throughout our loop and it is only 1 - but this is what it prints out below.


    <?xml version="1.0" encoding="utf-16"?>
    <xs:schema elementFormDefault="qualified" version="1.0" xmlns:xs="http://www.w3.org/2001/XMLSchema">
    <xs:element name="Orders">
    <xs:complexType>
    <xs:sequence>
    <xs:element name="Order">
    <xs:complexType>
    <xs:sequence />
    </xs:complexType>
    </xs:element>
    <xs:element name="Customer">
    <xs:complexType>
    <xs:sequence />
    </xs:complexType>
    </xs:element>
    </xs:sequence>
    </xs:complexType>
    </xs:element>
    </xs:schema>


    --Note we also tried the items collection before compiling but it only gets the "top" level items...it doesn't read/find nested items/elements. I found that detail on a Microsoft webcast. I guess this just gets top level elements also...??


    Thanks.


    Sincerely,
    Wendy Attenberger


    • Targeting a nested child element
      2003-02-24 12:03:30 Priyamvadha Lakshminarayanan [Reply]

      The Elements property returns all top-level elements added to the schema. To access the child elements, you need to access the complex type of the element and its LocalElements property.


      foreach (XmlSchemaElement parentElem in schema.Elements.Values) {
      XmlSchemaComplexType ct = parentElem.ElementType as XmlSchemaComplexType;
      if (ct != null) {
      foreach(XmlSchemaElement childElem in ct.LocalElements.Values) {
      Console.WriteLine(childElem.QualifiedName.ToString());
      }
      }
      }


      Thanks,
      Priya

      • Targeting a nested child element
        2003-02-25 06:10:51 Wendy Attenberger [Reply]

        I used the following line in vb.net:


        Dim ct As XmlSchemaComplexType = parentElement.ElementType


        to replace this one in c#:


        XmlSchemaComplexType ct = parentElem.ElementType as XmlSchemaComplexType


        On the next line, I try to access the LocalElements collection - but I don't see a LocalElements collection in my intellisense. (I see Particle, Qualified Name, etc.) I don't think I have the right syntax in vb.net.


        You wouldn't have to have any ideas on the syntax for vb.net? Thanks again...this has been extremely helpful. I have found very little sample code in this area.

        • Targeting a nested child element
          2003-02-25 14:00:11 Priyamvadha Lakshminarayanan [Reply]

          My mistake, the LocalElements property is not available in Version 1 of the .NET Framework.


          Try the following sample instead:


          foreach (XmlSchemaElement parentElement in schema.Elements.Values) {


          XmlSchemaComplexType ct = parentElement.ElementType as XmlSchemaComplexType; //Casting to complex type
          if (ct != null) {
          XmlSchemaSequence seq = (XmlSchemaSequence)ct.ContentTypeParticle; //Assuming it’s a sequence of elements
          foreach(XmlSchemaParticle p in seq.Items) {
          XmlSchemaElement elem = p as XmlSchemaElement; //Check if particle in seq is XmlSchemaElement
          if (elem != null)
          Console.WriteLine(elem.QualifiedName.ToString());
          }
          }


          Thanks,
          Priya

          • Targeting a nested child element
            2003-05-29 13:22:20 Carol Skelly [Reply]

            Hi..


            I also need to accomplish this in Vb.Net -- has anyone been successful with this?


            Also -- as you said the post-complilation Elements collection only contains top-level 'elements' -- so wouldn't we need to examine the Items collection instead to find any 'complextypes' or 'simpletypes'? ie:


            foreach (XmlSchemaElement parentElement in schema.Items) {


            Thanks for this great article! And please let me know if you can shed any light on the matter of traversing nested schema elements. Thanks!




            • Targeting a nested child element
              2004-11-15 06:36:28 Qoheleth [Reply]

              VB.NET version of the above code would be


              Dim parentElement As XmlSchemaElement
              For Each parentElement In schema.Elements.Values
              'Try Casting to complex type
              Dim ct As XmlSchemaComplexType
              Try
              ct = DirectCast(parentElement.ElementType, XmlSchemaComplexType)
              Catch ex As InvalidCastException
              ct = Nothing
              End Try
              If Not ct Is Nothing Then
              Dim seq As XmlSchemaSequence = DirectCast(ct.ContentTypeParticle, XmlSchemaSequence)


              'Assuming it’s a sequence of elements
              Dim p As XmlSchemaParticle
              For Each p In seq.Items
              'Check if particle in seq is XmlSchemaElement
              Dim elem As XmlSchemaElement
              Try
              elem = DirectCast(p, XmlSchemaElement)
              Catch ex As InvalidCastException
              elem = Nothing
              End Try


              If Not elem Is Nothing Then
              Console.WriteLine(elem.QualifiedName.ToString());
              End If
              Next
              End If
              Next





            • Targeting a nested child element
              2003-05-29 14:53:04 Priyamvadha Lakshminarayanan [Reply]

              To access nested elements, you would need to traverse down the content model of the parent element as shown in my previous post.


              The elements in the post-compilation Elements collection each have an ElementType property which exposes the compiled complex type or simple type of that element. To get to the child elements, you need to navigate down the complex type.


              The items collection is used to add items to the schema prior to compilation.