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
- 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;