Peupler une classe C++ depuis XML avec IXMLSerializable dans PLCnext
Résumé
Saviez-vous que les PLCnext Common Classes intègrent nativement la sérialisation XML ? Cet article montre comment utiliser l'interface IXmlSerializable pour peupler les données d'une classe C++.
La description de l'interface se trouve dans la documentation API des PLCnext Common Classes.
Prérequis
Article rédigé avec :
PLCnext Firmware: 2020.6 LTS
PLCnext C++ SDK pour Linux 64 Bit 2020.6 LTS
Les données
Nous voulons peupler notre classe avec le fichier de configuration suivant :
<?xml version="1.0" encoding="UTF-8"?>
<MyConfigDocument schemaVersion="1.0">
<Server dnsName="server.domain.tld" />
<FileList>
<File path="$ARP_DATA_DIR$/Services/MyComponent/file1.txt" />
<File path="$ARP_DATA_DIR$/Services/MyComponent/file2.txt" />
</FileList>
</MyConfigDocument>
La notation $ARP_DATA_DIR$ est un placeholder pour variable d'environnement, ici ARP_DATA_DIR. Les variables d'environnement Arp définies se trouvent dans le fichier de paramètres du périphérique cible /etc/plcnext/Device.acf.settings.
Pour lire les données depuis un fichier XML, il faut implémenter l'interface IXMLSerializable pour notre classe. Pour simplifier, notre classe n'a que deux éléments : un nom DNS et un vecteur de chemins de fichiers.
#pragma once
#include "Arp/System/Core/Arp.h"
#include "Arp/System/Commons/Xml/IXmlSerializable.hpp"
#include <vector>
namespace MyComponent {
class MyConfiguration : public Arp::System::Commons::Xml::IXmlSerializable {
public:
MyConfiguration() = default;
~MyConfiguration() = default;
// Interface IXMLSerializable
public:
void ReadXml(Arp::System::Commons::Xml::XmlReader& reader, Arp::System::Commons::Xml::XmlSerializationContext& context) override;
void WriteXml(Arp::System::Commons::Xml::XmlWriter& writer, Arp::System::Commons::Xml::XmlSerializationContext& context) override;
// Les données
public:
Arp::String DnsName{""};
std::vector<Arp::String> FileList;
// Méthodes de support
private:
void readFileList(Arp::System::Commons::Xml::XmlReader& reader, Arp::System::Commons::Xml::XmlSerializationContext& context);
void readFile(Arp::System::Commons::Xml::XmlReader& reader, Arp::System::Commons::Xml::XmlSerializationContext& context);
};
} // namespace MyComponent
Implémentation
Il faut implémenter les méthodes ReadXml et WriteXml.
WriteXml est simple : on ne veut pas écrire, seulement lire. ReadXml est appelée pour lire les données du fichier XML.
#include "MyConfiguration.hpp"
namespace MyComponent {
void MyConfiguration::WriteXml(Arp::System::Commons::Xml::XmlWriter& writer, Arp::System::Commons::Xml::XmlSerializationContext& context) {
// pas d'opération
return;
}
void MyConfiguration::ReadXml(Arp::System::Commons::Xml::XmlReader& reader, Arp::System::Commons::Xml::XmlSerializationContext& context) {
Arp::String elementName;
while (reader.TryReadStartElement(elementName)) {
if (elementName == Arp::System::Commons::Xml::XmlSerializationContext::IncludesXmlName) {
context.ReadIncludesElement(reader);
} else if (elementName == "Server") {
this->DnsName = reader.GetAttributeValue<Arp::String>("dnsName");
reader.ReadEndElement();
} else if (elementName == "FileList") {
this->readFileList(reader, context);
} else {
context.InvalidXmlElementOccurs(reader, elementName);
reader.ReadEndElement();
}
}
}
void MyConfiguration::readFileList(Arp::System::Commons::Xml::XmlReader& reader, Arp::System::Commons::Xml::XmlSerializationContext& context) {
if (reader.IsEmptyElement()){ return; }
if (reader.ReadToDescendant("File")) {
this->readFile(reader, context);
while (reader.ReadToNextSibling("File")) {
this->readFile(reader, context);
}
} else {
reader.ReadEndElement();
}
}
void MyConfiguration::readFile(Arp::System::Commons::Xml::XmlReader& reader, Arp::System::Commons::Xml::XmlSerializationContext& context) {
// Utiliser 'context.ResolvePath' pour remplacer les placeholders dans le chemin.
auto file = Arp::String(context.ResolvePath(reader.GetAttributeValue<Arp::String>("path")));
this->FileList.push_back(file);
reader.ReadEndElement();
}
} // namespace MyComponent
Lire les données
On peut maintenant utiliser notre classe avec la classe XMLConfigDocument dans la méthode LoadConfig pour charger les données dans notre classe.
void MyComponent::LoadConfig() {
// charger la config du projet ici
using namespace Arp::System::Commons;
this->log.Info("LoadConfig");
// Premier argument doit correspondre au nom de l'élément racine XML.
// Notre instance MyConfiguration this->config sera peuplée.
Xml::XmlConfigDocument configDoc("MyConfigDocument", this->config);
if (!Io::File::Exists(this->settingsPath)) {
this->log.Error("Le fichier de configuration '{}' n'existe pas.", this->settingsPath);
return;
}
try {
configDoc.Load(this->settingsPath);
} catch (const Arp::Exception& e) {
this->log.Error(e.GetMessage());
throw InvalidConfigException(e.GetMessage());
}
}
Technologie industrielle
- Utiliser l'interface en ligne de commande avec SPICE
- Comment la Technologie Transforme la Fabrication : De l'Impression 3D aux Jumeaux Numériques
- Maîtriser Java BufferedReader : Gestion efficace des flux de caractères
- Maîtriser les opérations sur les fichiers Java avec java.io – Création, lecture, écriture et suppression
- Interface vs Classe Abstraite en Java : Comment Choisir la Bonne Abstraction
- Comprendre les interfaces Java : Définitions, Usages et Bonnes pratiques
- Choisir le bon moteur anti-explosion : guide complet
- Types de matrices (dies) en fabrication : Estampage, tréfilage, moulage
- Comment les points de test améliorent les tests PCB et l'efficacité de la production