Заполнение C++ класса из XML с использованием IXMLSerializable в PLCnext
Аннотация
Знаете ли вы, что в PLCnext Common Classes есть встроенная поддержка XML-сериализации? Эта статья показывает, как использовать интерфейс IXmlSerializable для заполнения данных в C++ классе.
Описание интерфейса можно найти в API документации PLCnext Common Classes.
Требования
Статья написана со следующей настройкой:
PLCnext Firmware: 2020.6 LTS
PLCnext C++ SDK для Linux 64 Bit 2020.6 LTS
Данные
Мы хотим заполнить наш класс следующим конфигурационным файлом.
<?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>
Нотация $ARP_DATA_DIR$ — это плейсхолдер для переменной окружения, в данном случае ARP_DATA_DIR. Определенные переменные окружения Arp можно найти в файле настроек устройства на целевом устройстве /etc/plcnext/Device.acf.settings.
Чтобы читать данные из XML-файла, нам нужно реализовать интерфейс IXMLSerializable для нашего класса. Для простоты наш класс имеет только два элемента данных: DNS-имя и вектор путей к файлам.
#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;
// IXMLSerializable interface
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;
// Данные
public:
Arp::String DnsName{""};
std::vector<Arp::String> FileList;
// Вспомогательные методы
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
Реализация
Мы должны реализовать методы ReadXml и WriteXml.
Метод WriteXml прост — мы не хотим писать, мы только хотим читать данные из XML-файла. Метод ReadXml вызывается, когда мы хотим прочитать данные из XML-файла.
#include "MyConfiguration.hpp"
namespace MyComponent {
void MyConfiguration::WriteXml(Arp::System::Commons::Xml::XmlWriter& writer, Arp::System::Commons::Xml::XmlSerializationContext& context) {
// no operation.
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) {
// Используем 'context.ResolvePath' для замены плейсхолдеров в пути.
auto file = Arp::String(context.ResolvePath(reader.GetAttributeValue<Arp::String>("path")));
this->FileList.push_back(file);
reader.ReadEndElement();
}
} // namespace MyComponent
Чтение данных
Теперь мы можем использовать наш класс с классом XMLConfigDocument в методе LoadConfig для загрузки данных в наш класс.
void MyComponent::LoadConfig() {
// load project config here
using namespace Arp::System::Commons;
this->log.Info("LoadConfig");
// Первый аргумент должен соответствовать имени корневого XML элемента.
// Наш экземпляр MyConfiguration this->config будет заполнен.
Xml::XmlConfigDocument configDoc("MyConfigDocument", this->config);
if (!Io::File::Exists(this->settingsPath)) {
this->log.Error("Configuration file '{}' does not exist.", this->settingsPath);
return;
}
try {
configDoc.Load(this->settingsPath);
} catch (const Arp::Exception& e) {
this->log.Error(e.GetMessage());
throw InvalidConfigException(e.GetMessage());
}
}
Промышленные технологии
- Работа с интерфейсом командной строки в SPICE
- Как технологии трансформируют производство: от 3D-печати к цифровым двойникам
- Освоение Java BufferedReader: эффективная обработка символьных потоков
- Мастерство файловых операций в Java с java.io: создание, чтение, запись и удаление
- Интерфейс vs Абстрактный класс в Java: Как выбрать правильную абстракцию
- Интерфейсы в Java: определение, использование и лучшие практики
- Выбор правильного взрывозащищенного двигателя: всеобъемлющее руководство
- Типы штампов в производстве: штамповка, вытягивание и литье
- Как тестовые точки улучшают тестирование печатных плат и производственную эффективность
- Как автоматизация трансформирует производство: Повышение эффективности и сокращение затрат