FAQ C++/CLI et VC++.Net

FAQ C++/CLI et VC++.NetConsultez toutes les FAQ
Nombre d'auteurs : 29, nombre de questions : 248, création le 22 février 2013
Sommaire→Intéraction du C++/CLI avec le framework .Net→Fichiers, Répertoires, Disques→XML- Comment lire un fichier Xml avec les classes de l'espace de noms System::Xml::Xpath ?
- Comment créer un XmlNamespaceManager en se basant sur un fichier Xml ?
- Comment valider un fichier XML avec un schéma XSD ?
- Comment sérialiser et désérialiser un objet simple en XML ?
- Comment sérialiser un objet en ignorant les références circulaires ?
Voici un exemple basique de lecture d'un fichier Xml en se servant non pas de la classe XmlDocument mais des classes de l'espace de noms System::Xml::Xpath. Deux cas sont distingués, les fichiers Xml de base et ceux avec un espace de noms.
<Recordbook>
<Records>
<Record>
<FirstValue>10</FirstValue>
<SecondValue>51</SecondValue>
</Record>
<Record>
<FirstValue>25</FirstValue>
<SecondValue>38</SecondValue>
</Record>
</Records>
</Recordbook>using namespace System::Xml::XPath;
void TraiteXml(String ^ fileName)
{
XPathDocument ^ doc = gcnew XPathDocument(fileName);
XPathNavigator ^ nav = doc->CreateNavigator();
// On récupère un XPathNodeIterator sur les Record
XPathNodeIterator ^ iter = nav->Select("Recordbook/Records/Record");
// Pour chaque Record
while (iter->MoveNext())
{
// On récupère l'info FirstValue
String ^ firstValue = iter->Current->SelectSingleNode("FirstValue")->Value;
// On récupère l'info SecondValue
String ^ secondValue = iter->Current->SelectSingleNode("SecondValue")->Value;
Console::WriteLine("{0},{1}", firstValue, secondValue);
}
}<rd:Recordbook xmlns:rd="http://myexemple/myschema/record">
<rd:Records>
<rd:Record>
<rd:FirstValue>10</rd:FirstValue>
<rd:SecondValue>51</rd:SecondValue>
</rd:Record>
<rd:Record>
<rd:FirstValue>25</rd:FirstValue>
<rd:SecondValue>38</rd:SecondValue>
</rd:Record>
</rd:Records>
</rd:Recordbook>using namespace System::Xml;
void TraiteXmlEspaceNom(String ^ fileName)
{
XPathDocument ^ doc = gcnew XPathDocument(fileName);
XPathNavigator ^ nav = doc->CreateNavigator();
// On ajoute la gestion des espaces de noms
XmlNamespaceManager ^ mgr = gcnew XmlNamespaceManager(nav->NameTable);
mgr->AddNamespace("rd", "http://myexemple/myschema/record");
// On récupère un XPathNodeIterator sur les Record
XPathNodeIterator ^ iter = nav->Select("rd:Recordbook/rd:Records/rd:Record", mgr);
// Pour chaque Record
while (iter->MoveNext())
{
// On récupère l'info FirstValue
String ^ firstValue = iter->Current->SelectSingleNode("FirstValue", mgr)->Value;
// On récupère l'info SecondValue
String ^ secondValue = iter->Current->SelectSingleNode("SecondValue", mgr)->Value;
Console::WriteLine("{0},{1}", firstValue, secondValue);
}
}Remarque :
Attention, XPath est sensible à la casse. Le nom des balises doit correspondre exactement.
nav->Select("Recordbook/Records/Record");et
nav->Select("Recordbook/Records/record");ne représentent pas la même chose.
Précédemment, nous avons vu comment lire un fichier avec les classes XPath. Dans le cas de la présence de namespace dans le fichier Xml, nous avons utilisé la classe XmlNamespaceManager pour gérer ces espaces de noms. Le seul défaut c'est que nous alimentions manuellement ces données, nous allons donc maintenant créer ce XmlNamespaceManager de manière automatique.
<rd:Recordbook xmlns:rd="http://myexemple/myschema/record">
<rd:Records>
<rd:Record>
<rd:FirstValue>10</rd:FirstValue>
<rd:SecondValue>51</rd:SecondValue>
</rd:Record>
<rd:Record>
<rd:FirstValue>25</rd:FirstValue>
<rd:SecondValue>38</rd:SecondValue>
</rd:Record>
</rd:Records>
</rd:Recordbook>using namespace System::Collections::Generic;
using namespace System::Xml;
using namespace System::Xml::XPath;
XmlNamespaceManager ^ GetXmlNamespaceManager(XPathNavigator ^ nav)
{
XmlNamespaceManager ^ mgr = nullptr;
nav->MoveToFirstChild();
for each (KeyValuePair<String ^, String ^> ^ keyPair in nav->GetNamespacesInScope(XmlNamespaceScope::Local))
{
if (mgr == nullptr)
mgr = gcnew XmlNamespaceManager(nav->NameTable);
mgr->AddNamespace(keyPair->Key, keyPair->Value);
}
nav->MoveToRoot();
return mgr;
}
void TraiteXml(String ^ fileName)
{
XPathDocument ^ doc = gcnew XPathDocument(fileName);
XPathNavigator ^ nav = doc->CreateNavigator();
XmlNamespaceManager ^ mgr = GetXmlNamespaceManager(nav);
if (mgr != nullptr)
{
XPathNodeIterator ^ iter = nav->Select("rd:Recordbook/rd:Records/rd:Record", mgr);
while (iter->MoveNext())
{
String ^ firstValue = iter->Current->SelectSingleNode("rd:FirstValue", mgr)->Value;
String ^ secondValue = iter->Current->SelectSingleNode("rd:SecondValue", mgr)->Value;
Console::WriteLine("{0},{1}", firstValue, secondValue);
}
}
}Si vous devez valider un fichier XML avec un schéma XSD, voici la marche à suivre.
<?xml version="1.0"?>
<catalog>
<!--
<title>2000-10-01</title>
-->
<book id="bk101">
<author>Gambardella, Matthew</author>
<title>XML Developer's Guide</title>
<genre>Computer</genre>
<prices>44.95</price>
<publish_date>2000-10-01</publish_dates>
<description>An in-depth look at creating applications with XML.</description>
</book>
</catalog><xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<xsd:element name="catalog" type="CatalogData"/>
<xsd:complexType name="CatalogData">
<xsd:sequence>
<xsd:element name="book" type="bookdata" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="bookdata">
<xsd:sequence>
<xsd:element name="author" type="xsd:string"/>
<xsd:element name="title" type="xsd:string"/>
<xsd:element name="genre" type="xsd:string"/>
<xsd:element name="price" type="xsd:float"/>
<xsd:element name="publish_date" type="xsd:date"/>
<xsd:element name="description" type="xsd:string"/>
</xsd:sequence>
<xsd:attribute name="id" type="xsd:string"/>
</xsd:complexType>
</xsd:schema>using namespace System::Xml;
using namespace System::Xml::Schema;
public ref class ValidXml
{
private:
static bool error;
static void XmlValidationError(Object ^ sender, ValidationEventArgs ^ e)
{
// code à exécuter si erreur à la validation du Xml suivant le schéma Xsd
// on passe autant de fois dans ce code qu'il y a d'erreurs
Debug::WriteLine(e->Exception->Message);
error = true;
}
public:
static Boolean isXmlValide(String ^xsdFile, String ^xmlFile)
{
error = false;
try
{
XmlReaderSettings ^ settings = gcnew XmlReaderSettings();
settings->Schemas->Add(nullptr, xsdFile);
settings->ValidationType = ValidationType::Schema;
settings->ValidationEventHandler += gcnew ValidationEventHandler(XmlValidationError);
XmlReader ^ reader = XmlReader::Create(xmlFile, settings);
while (reader->Read()) ;
} catch (Exception ^)
{
return false;
}
return !error;
}
};
void main () // test
{
Console::WriteLine(ValidXml::isXmlValide("c:\\books.xsd", "c:\\books.xml"));
}On utlise l'objet XmlSerializer pour effectuer une sérialisation et une désérialisation en xml.
Soit l'objet suivant :
public ref class Personne
{
private:
String ^_login;
String ^_pwd;
int _age;
public:
Personne(){}
Personne(String ^login, String ^pwd, int age) : _login(login), _pwd(pwd), _age(age) {}
property String ^Login
{
String ^ get() { return _login; }
void set(String ^value) { _login = value; }
}
[System::Xml::Serialization::XmlIgnore()]
property String ^Pwd
{
String^ get() { return _pwd; }
void set(String ^value) { _pwd = value; }
}
property int Age
{
int get() { return _age; }
void set(int value) { _age = value; }
}
virtual String^ ToString() override
{
return String::Format("Login : {0} - Mot de passe : {1} - Age : {2}", _login, _pwd, _age);
}
};
Je peux choisir délibérément de ne pas sérialiser certaines propriétés. Par exemple, la propriété Pwd ne sera pas sérialisée grâce à l'attribut XmlIgnore.
NB : Pour être sérialisable, une classe doit-être publique, les membres à sérialiser doivent l'être également. La classe devra également posséder un constructeur par défaut et une propriété set pour chaque propriété get.
On effectue la sérialisation ainsi :
Personne ^moi = gcnew Personne("Nico", "Abcd", 28);
System::Xml::Serialization::XmlSerializer ^ sr = gcnew System::Xml::Serialization::XmlSerializer(Personne::typeid);
System::IO::StreamWriter ^ writer = gcnew System::IO::StreamWriter("fichier.xml");
try
{
sr->Serialize(writer, moi);
}
catch (Exception ^ e)
{
Console::WriteLine("Impossible de sérialiser : " + e->Message);
}
finally
{
writer->Close();
}On utilise ici directement un StreamWriter pour enregistrer un fichier xml, mais il est envisageable d'utiliser d'autres surcharges, pour par exemple obtenir une chaine.
La désérialisation se déroule ainsi :
Personne ^moiClone;
System::IO::StreamReader ^reader = gcnew System::IO::StreamReader("fichier.xml");
try
{
moiClone = (Personne^)sr->Deserialize(reader);
Console::WriteLine(moiClone->ToString());
}
catch (Exception ^ e)
{
Console::WriteLine("Impossible de désérialiser : " + e->Message);
}
finally
{
reader->Close();
}Notez bien sur que l'appel à ToString restitue une chaine vide pour le mot de passe car cette propriété n'a pas été sérialisée.
Prenons le cas de deux classes, classA et classB :
ref class classB;
[Serializable()]
public ref class classA
{
private:
int m_MembreInt;
classB ^m_B;
public:
property int MembreInt;
property classB ^B;
};
[Serializable()]
public ref class classB
{
private:
float m_MembreFloat;
classA ^m_A;
public:
property float MembreFloat;
property classA ^A;
};Comme vous pouvez le voir, classA a une référence vers classB et classB a une référence vers classA. Si nous essayons de sérialiser un objet de type classA ou classB (et à condition que les références correspondantes ne soient pas nulles), nous aurons une erreur de référence circulaire et la sérialisation s'avèrera impossible :
classA ^objA = gcnew classA();
classB ^objB = gcnew classB();
objA->B = objB;
objB->A = objA;
// sérialisation
System::Xml::Serialization::XmlSerializer ^ sr = gcnew System::Xml::Serialization::XmlSerializer(classA::typeid);
System::IO::StreamWriter ^ writer = gcnew System::IO::StreamWriter("fichier.xml");
sr->Serialize(writer, objA);
writer->Close();Le code ci dessous génèrera le message d'erreur suivant à l'exécution :
Référence circulaire détectée lors de la sérialisation d'un objet de type ConsoleApplication2.classA.Afin de retirer les références circulaires, il suffira d'exclure les objets en question de la sérialisation en utilisant l'attribut System.Xml.Serialization.XmlIgnore sur les propriétés concernées :
[System::Xml::Serialization::XmlIgnore()]
property classB ^B;et
[System::Xml::Serialization::XmlIgnore()]
property classA ^A;


