Validar un XML contra XSD referenciado en C #

161

Tengo un archivo XML con una ubicación de esquema especificada como esta:

xsi:schemaLocation="someurl ..\localSchemaPath.xsd"

Quiero validar en C #. Visual Studio, cuando abro el archivo, lo valida contra el esquema y enumera los errores perfectamente. Sin embargo, de alguna manera, parece que no puedo validarlo automáticamente en C # sin especificar el esquema para validar de esta manera:

XmlDocument asset = new XmlDocument();

XmlTextReader schemaReader = new XmlTextReader("relativeSchemaPath");
XmlSchema schema = XmlSchema.Read(schemaReader, SchemaValidationHandler);

asset.Schemas.Add(schema);

asset.Load(filename);
asset.Validate(DocumentValidationHandler);

¿No debería poder validar con el esquema especificado en el archivo XML automáticamente? Qué me estoy perdiendo ?

jfclavette
fuente
1
Consulte el ejemplo de MSDN: msdn.microsoft.com/en-us/library/…

Respuestas:

167

Debe crear una instancia de XmlReaderSettings y pasarla a su XmlReader cuando la cree. Luego, puede suscribirse a ValidationEventHandlerla configuración para recibir errores de validación. Su código terminará luciendo así:

using System.Xml;
using System.Xml.Schema;
using System.IO;

public class ValidXSD
{
    public static void Main()
    {

        // Set the validation settings.
        XmlReaderSettings settings = new XmlReaderSettings();
        settings.ValidationType = ValidationType.Schema;
        settings.ValidationFlags |= XmlSchemaValidationFlags.ProcessInlineSchema;
        settings.ValidationFlags |= XmlSchemaValidationFlags.ProcessSchemaLocation;
        settings.ValidationFlags |= XmlSchemaValidationFlags.ReportValidationWarnings;
        settings.ValidationEventHandler += new ValidationEventHandler(ValidationCallBack);

        // Create the XmlReader object.
        XmlReader reader = XmlReader.Create("inlineSchema.xml", settings);

        // Parse the file. 
        while (reader.Read()) ;

    }
    // Display any warnings or errors.
    private static void ValidationCallBack(object sender, ValidationEventArgs args)
    {
        if (args.Severity == XmlSeverityType.Warning)
            Console.WriteLine("\tWarning: Matching schema not found.  No validation occurred." + args.Message);
        else
            Console.WriteLine("\tValidation error: " + args.Message);

    }
}
Chris McMillan
fuente
44
1 aunque debe actualizar para usar usingla cláusula de integridad :)
iAbstract
55
Si está buscando comparar con un archivo XSD, agregue la siguiente línea al código anterior: settings.Schemas.Add ("YourDomainHere", "yourXSDFile.xsd");
Jeff Fol
55
Para obtener la Línea # y la posición # del error simplemente use: args.Exception.LineNumber ... en ValidationCallBack
user610064
1
¿Qué pasa si el esquema que tengo no tiene un espacio de nombres?
árbol
1
Usando lambda , mejor en mi humilde opinión, más código de claridad settings.ValidationEventHandler += (o, args) => { errors = true; // More code };
Kiquenet
107

Una forma más simple, si está utilizando .NET 3.5, es usar XDocumenty XmlSchemaSetvalidar.

XmlSchemaSet schemas = new XmlSchemaSet();
schemas.Add(schemaNamespace, schemaFileName);

XDocument doc = XDocument.Load(filename);
string msg = "";
doc.Validate(schemas, (o, e) => {
    msg += e.Message + Environment.NewLine;
});
Console.WriteLine(msg == "" ? "Document is valid" : "Document invalid: " + msg);

Consulte la documentación de MSDN para obtener más ayuda.

Salsa
fuente
2
Ese método requiere que conozca el esquema de antemano en lugar de tomar el esquema en línea del xml.
Lankymart
esto funciona bien pero arroja un error cuando el documento xml contiene alguna etiqueta html como <catalog> my <i> new </i> catalog .... </catalog> en el caso anterior, las etiquetas html como "<i>" crean un problema ya que es el valor de "<catálogo>" ... cómo validarlo
Anil Purswani
66
@AnilPurswani: Si desea poner HTML en un documento XML, debe envolverlo en CDATA. <catalog><![CDATA[my <i> new </i> catalog....]]></catalog>es la forma correcta de hacer eso.
p0lar_bear
Simple y elegante! Esto funciona muy bien cuando se valida contra un conjunto de esquemas fijos (que es nuestro caso, y uno grande con múltiples carpetas y archivos). Ya estoy pensando en almacenar en caché el XmlSchemaSet para ser reutilizado entre llamadas al Validador. ¡Muchas gracias!
Adail Retamal
20

El siguiente ejemplo valida un archivo XML y genera el error o advertencia apropiado.

using System;
using System.IO;
using System.Xml;
using System.Xml.Schema;

public class Sample
{

    public static void Main()
    {
        //Load the XmlSchemaSet.
        XmlSchemaSet schemaSet = new XmlSchemaSet();
        schemaSet.Add("urn:bookstore-schema", "books.xsd");

        //Validate the file using the schema stored in the schema set.
        //Any elements belonging to the namespace "urn:cd-schema" generate
        //a warning because there is no schema matching that namespace.
        Validate("store.xml", schemaSet);
        Console.ReadLine();
    }

    private static void Validate(String filename, XmlSchemaSet schemaSet)
    {
        Console.WriteLine();
        Console.WriteLine("\r\nValidating XML file {0}...", filename.ToString());

        XmlSchema compiledSchema = null;

        foreach (XmlSchema schema in schemaSet.Schemas())
        {
            compiledSchema = schema;
        }

        XmlReaderSettings settings = new XmlReaderSettings();
        settings.Schemas.Add(compiledSchema);
        settings.ValidationEventHandler += new ValidationEventHandler(ValidationCallBack);
        settings.ValidationType = ValidationType.Schema;

        //Create the schema validating reader.
        XmlReader vreader = XmlReader.Create(filename, settings);

        while (vreader.Read()) { }

        //Close the reader.
        vreader.Close();
    }

    //Display any warnings or errors.
    private static void ValidationCallBack(object sender, ValidationEventArgs args)
    {
        if (args.Severity == XmlSeverityType.Warning)
            Console.WriteLine("\tWarning: Matching schema not found.  No validation occurred." + args.Message);
        else
            Console.WriteLine("\tValidation error: " + args.Message);

    }
}

El ejemplo anterior usa los siguientes archivos de entrada.

<?xml version='1.0'?>
<bookstore xmlns="urn:bookstore-schema" xmlns:cd="urn:cd-schema">
  <book genre="novel">
    <title>The Confidence Man</title>
    <price>11.99</price>
  </book>
  <cd:cd>
    <title>Americana</title>
    <cd:artist>Offspring</cd:artist>
    <price>16.95</price>
  </cd:cd>
</bookstore>

books.xsd

<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
    xmlns="urn:bookstore-schema"
    elementFormDefault="qualified"
    targetNamespace="urn:bookstore-schema">

 <xsd:element name="bookstore" type="bookstoreType"/>

 <xsd:complexType name="bookstoreType">
  <xsd:sequence maxOccurs="unbounded">
   <xsd:element name="book"  type="bookType"/>
  </xsd:sequence>
 </xsd:complexType>

 <xsd:complexType name="bookType">
  <xsd:sequence>
   <xsd:element name="title" type="xsd:string"/>
   <xsd:element name="author" type="authorName"/>
   <xsd:element name="price"  type="xsd:decimal"/>
  </xsd:sequence>
  <xsd:attribute name="genre" type="xsd:string"/>
 </xsd:complexType>

 <xsd:complexType name="authorName">
  <xsd:sequence>
   <xsd:element name="first-name"  type="xsd:string"/>
   <xsd:element name="last-name" type="xsd:string"/>
  </xsd:sequence>
 </xsd:complexType>

</xsd:schema>
Soroush
fuente
18

personalmente estoy a favor de validar sin una devolución de llamada:

public bool ValidateSchema(string xmlPath, string xsdPath)
{
    XmlDocument xml = new XmlDocument();
    xml.Load(xmlPath);

    xml.Schemas.Add(null, xsdPath);

    try
    {
        xml.Validate(null);
    }
    catch (XmlSchemaValidationException)
    {
        return false;
    }
    return true;
}

(ver la publicación de Timiz0r en Validación de esquema XML síncrono? .NET 3.5 )

FrankyHollywood
fuente
9
La devolución de llamada le proporciona información adicional sobre qué línea en su xml no es correcta. Este método es muy binario, ya sea correcto o incorrecto :)
FrankyHollywood
13

Tuve que hacer este tipo de validación automática en VB y así es como lo hice (convertido a C #):

XmlReaderSettings settings = new XmlReaderSettings();
settings.ValidationType = ValidationType.Schema;
settings.ValidationFlags = settings.ValidationFlags |
                           Schema.XmlSchemaValidationFlags.ProcessSchemaLocation;
XmlReader XMLvalidator = XmlReader.Create(reader, settings);

Luego me suscribí al settings.ValidationEventHandlerevento mientras leía el archivo.

Welbog
fuente