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 :
- Jouer avec le XML – partie 1 – Présentation
- Jouer avec le XML – partie 2 – Utilitaire XSD.exe
- Jouer avec le XML – partie 3 – (dé)sérialisation de classes et attributs
- Jouer avec le XML – partie 4 – Validation d’un XML avec un XSD
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++ !
-
30/11/2012 à 20:50 | #1Jouer avec le XML – partie 3 – (dé)sérialisation de classes et attributs « kerrubin's blog

