Accueil > .Net, C#, Développement, XML, XSD > Jouer avec le XML – partie 4 – Validation d’un XML avec un XSD

Jouer avec le XML – partie 4 – Validation d’un XML avec un XSD

Last but not least, nous allons voir ici comment valider un XML avec un XSD.
Le tout, avec .Net, naturellement.
Du coup, comme ce billet est censé être plus court que les autres, je vais mettre quelques outils utiles.

Voici les billets en rapport :

 

Valider un XML

 

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml;
using System.Xml.Schema;

namespace KR.Sample
{
    public class XmlValidator
    {
        private ValidationErrorCollection errors;

        /// <summary>
        /// Validation d'un fichier XML.
        /// </summary>
        /// <param name="xmlFileName">Chemin complet du fichier XML.</param>
        /// <param name="xsdFileName">Chemin complet du fichier XSD.</param>
        /// <returns>Collection d'erreurs.</returns>
        public ValidationErrorCollection ValidationFluxXml(String xmlFileName, String xsdFileName)
        {
            errors = new ValidationErrorCollection();
            try
            {
                XmlReaderSettings settings = GetSettings(xsdFileName);
                using (XmlReader doc = XmlReader.Create(xmlFileName, settings))
                {
                    while (doc.Read()) { }
                }
                return errors;
            }
            catch (Exception e)
            {
                errors.Add(new ValidationError(e));
                return errors;
            }
        }

        /// <summary>
        /// Validation d'un fichier XML.
        /// </summary>
        /// <param name="xmlFileName">Stream contenant le XML.</param>
        /// <param name="xsdFileName">Chemin complet du fichier XSD.</param>
        /// <returns>Collection d'erreurs.</returns>
        public ValidationErrorCollection Valider(Stream stream, String xsdFileName)
        {
            errors = new ValidationErrorCollection();
            try
            {
                stream.Position = 0;
                XmlReaderSettings settings = GetSettings(xsdFileName);
                using (XmlReader doc = XmlReader.Create(stream, settings))
                {
                    while (doc.Read()) { }
                }
                return errors;
            }
            catch (Exception e)
            {
                errors.Add(new ValidationError(e));
                return errors;
            }
        }

        /// <summary>
        /// Evènement pour gérer la validation.
        /// </summary>
        /// <param name="sender">Objet levant l'évènement.</param>
        /// <param name="args">Arguments de l'évènement.</param>
        private void XSDValidationHandler(Object sender, ValidationEventArgs args)
        {
            errors.Add(new ValidationError(args));
        }

        /// <summary>
        /// Permet de générer la configuration.
        /// </summary>
        /// <param name="xsdFileName">Chemin complet du fichier XSD.</param>
        /// <returns>Configuration du reader.</returns>
        private XmlReaderSettings GetSettings(String xsdFileName)
        {
            XmlReaderSettings settings = new XmlReaderSettings();
            settings.Schemas.Add(String.Empty, xsdFileName);
            settings.ValidationType = ValidationType.Schema;
            settings.Schemas.Compile();
            settings.ValidationEventHandler += new ValidationEventHandler(XSDValidationHandler);
            return settings;
        }
    }
    public class ValidationErrorCollection
    {
        public ValidationErrorCollection()
        {
            Errors = new List<ValidationError>();
        }

        public List<ValidationError> Errors { get; private set; }

        public Boolean HasError
        {
            get
            {
                return Errors
                    .Where(e => e.Severity == XmlSeverityType.Error)
                    .Any();
            }
        }

        public Boolean HasWarning
        {
            get
            {
                return Errors
                    .Where(e => e.Severity == XmlSeverityType.Warning)
                    .Any();
            }
        }

        public IEnumerable<String> GetMessages()
        {
            return Errors.Select(e => e.Message);
        }

        public IEnumerable<ValidationError> GetErrors()
        {
            return Errors.Where(e => e.Severity == XmlSeverityType.Error);
        }

        public IEnumerable<ValidationError> GetWarnings()
        {
            return Errors.Where(e => e.Severity == XmlSeverityType.Warning);
        }

        /// <summary>
        /// Permet de formater le message.
        /// </summary>
        /// <param name="pattern">{0} pour LineNumber, {1} pour LinePosition et {2} pour Message.</param>
        /// <returns>Liste des messages.</returns>
        public IEnumerable<String> FlattenMessages(String pattern)
        {
            return Errors.Select(e => String.Format(pattern, e.LineNumber, e.LinePosition, e.Message));
        }

        public void Add(ValidationError error)
        {
            Errors.Add(error);
        }

        public void Add(Exception exception)
        {
            Errors.Add(new ValidationError(exception));
        }
    }

    public class ValidationError
    {
        public Int32 LineNumber { get; set; }
        public Int32 LinePosition { get; set; }
        public String Message { get; set; }
        public XmlSeverityType Severity { get; set; }

        public ValidationError(ValidationEventArgs args)
        {
            if (args != null)
            {
                Severity = args.Severity;
                Message = args.Message;
                if (args.Exception != null)
                {
                    LineNumber = args.Exception.LineNumber;
                    LinePosition = args.Exception.LinePosition;
                }
            }
        }
        public ValidationError(Exception exception)
        {
            Severity = XmlSeverityType.Error;
            Message = exception.Message;
        }
    }
}

Le code n’est pas bien compliqué.
En somme, il revient à configurer un XmlReader via un XmlReaderSettings puis à lire le fichier pour lever tous les problèmes rencontré grâce à un évènement.
On obtient donc en sortie une collection avec les erreurs et avertissements qui vont bien.

Pour l’utilisation, on peut avoir :

            ValidationErrorCollection errors = new XmlValidator()
                .ValidationFluxXml(Path.Combine(path, "SampleXML3.xml"), Path.Combine(path, "SampleXML.xsd"));
            IEnumerable<String> messages = errors.FlattenMessages("Ligne : {0}, Message : {2}");

Ici, validation du dernier XML créé dans le billet précédent (l’héritage, avec James Pond) validé avec le XSD d’origine :

Ligne : 2, Message : L’élément ‘GOD’ n’est pas déclaré.

Comme on peut le voir, il n’y a pas tous les problèmes rencontrés.
De ce que j’ai remarqué, c’est qu’il s’arrête à la première balise invalide (balise non déclarée comme ici, absente alors qu’obligatoire…) mais si toutes les balises sont correctes, il a tendance à remonter tous les problèmes sur le format (entre 1 et 15 caractères, non respect du pattern…).

 

Lecture dans un XML

 

Je fais un très rapide point (que je développerais peut être une autre fois) sur la lecture dans un XML.
Il y a plusieurs moyen, par exemple si l’on veut récupérer les noms.

Voici deux méthodes :

            // Avec XPath
            Stopwatch sOldStyle = Stopwatch.StartNew();
            XmlDocument xml = new XmlDocument();
            xml.Load(Path.Combine(path, "SampleXml0.xml"));
            XmlNodeList nodes = xml.SelectNodes("EXPORT/USER/NOM");             // XPath pour obtenir les nodes NOM
            List<String> namesOldStyle = new List<String>();
            for (Int32 index = 0; index < nodes.Count; index++)                 // Boucle sur les nodes
                namesOldStyle.Add(nodes.Item(index).InnerText);
            sOldStyle.Start();

            // Avec Linq
            Stopwatch sLinq = Stopwatch.StartNew();
            XElement root = XElement.Load(Path.Combine(path, "SampleXml0.xml"));
            IEnumerable<String> namesLinq = root.Elements()                     // Tous les éléments : HEADER x1; USER x2
                            .Where(el => el.Name.LocalName.Equals("USER"))      // Filtre sur les éléments USER
                            .Elements()                                         // Tous les éléments de USER : NOM, PRENOM...
                            .Where(el => el.Name.LocalName.Equals("NOM"))       // Filtre sur NOM
                            .Select(el => el.Value);                            // Sélectionner la valeur
            sLinq.Start();

J’ai mis des stopwatchs, par curiosité.
Comme mon « volume » de base était faible, j’ai dupliqué les lignes. 140.000 fois.
Ca fait du 1.120.007 lignes pour un peu plus de 36Mo.
Sur 5 itérations, ça donne une moyenne de 950ms pour XPath (2.845.895 ticks sur le dernier) et 700ms pour Linq (2.154.975 ticks sur le dernier).
Finalement, celui qui ne suit plus, c’est Notepad++ !😀

Catégories :.Net, C#, Développement, XML, XSD

Laisser un commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l'aide de votre compte WordPress.com. Déconnexion / Changer )

Image Twitter

Vous commentez à l'aide de votre compte Twitter. Déconnexion / Changer )

Photo Facebook

Vous commentez à l'aide de votre compte Facebook. Déconnexion / Changer )

Photo Google+

Vous commentez à l'aide de votre compte Google+. Déconnexion / Changer )

Connexion à %s

%d blogueurs aiment cette page :