33#include <libxml/parser.h>
34#include <libxml/tree.h>
35#include <libxml/xpath.h>
36#include <libxml/xpathInternals.h>
55 xmlDocPtr
doc =
nullptr;
65struct XPathContextGuard
67 xmlXPathContextPtr
ctx =
nullptr;
72 xmlXPathFreeContext(
ctx);
77struct XPathObjectGuard
79 xmlXPathObjectPtr
obj =
nullptr;
84 xmlXPathFreeObject(
obj);
96std::string EscapeXPathLiteral(
const std::string& s)
102 if (s.find(
'\'') == std::string::npos)
104 return "'" + s +
"'";
106 std::ostringstream oss;
118 oss <<
"'" << cur <<
"'";
134 oss <<
"'" << cur <<
"'";
142std::vector<xmlNodePtr> XPathFind(xmlXPathContextPtr
ctx, xmlNodePtr node,
const std::string& expr)
144 xmlXPathContextPtr currentCtx =
ctx;
145 xmlNodePtr savedNode = currentCtx->node;
148 currentCtx->node = node;
151 XPathObjectGuard guard;
152 guard.obj = xmlXPathEvalExpression(
reinterpret_cast<const xmlChar*
>(expr.c_str()), currentCtx);
153 currentCtx->node = savedNode;
155 std::vector<xmlNodePtr> result;
156 if ((guard.obj ==
nullptr) || (guard.obj->nodesetval ==
nullptr))
160 for (
int i = 0; i < guard.obj->nodesetval->nodeNr; ++i)
162 result.push_back(guard.obj->nodesetval->nodeTab[i]);
168xmlNodePtr FindFirst(xmlXPathContextPtr
ctx, xmlNodePtr node,
const std::string& tag)
170 auto results = XPathFind(
ctx, node,
".//*[local-name()='" + tag +
"']");
171 return results.empty() ? nullptr : results.front();
175std::vector<xmlNodePtr> FindAll(xmlXPathContextPtr
ctx, xmlNodePtr node,
const std::string& tag)
177 return XPathFind(
ctx, node,
".//*[local-name()='" + tag +
"']");
181std::vector<xmlNodePtr> FindChildren(xmlXPathContextPtr
ctx, xmlNodePtr node,
const std::string& tag)
183 return XPathFind(
ctx, node,
"./*[local-name()='" + tag +
"']");
186std::string GetAttr(xmlNodePtr node,
const std::string& name,
const std::string& Default =
"")
192 xmlChar* val = xmlGetProp(node,
reinterpret_cast<const xmlChar*
>(name.c_str()));
197 std::string result(
reinterpret_cast<const char*
>(val));
202std::optional<std::string> GetAttrOpt(xmlNodePtr node,
const std::string& name)
208 xmlChar* val = xmlGetProp(node,
reinterpret_cast<const xmlChar*
>(name.c_str()));
213 std::string result(
reinterpret_cast<const char*
>(val));
224std::uint32_t ParseInt(
const std::optional<std::string>& spec)
230 const std::string& s = *spec;
236 if (s.size() > 3 && s[0] ==
'#' && s[1] ==
'1' && s[2] ==
'6' && s[3] ==
'#')
238 return static_cast<std::uint32_t
>(std::stoul(s.substr(4),
nullptr, 16));
240 if (s.size() > 2 && s[0] ==
'0' && (s[1] ==
'x' || s[1] ==
'X'))
242 return static_cast<std::uint32_t
>(std::stoul(s.substr(2),
nullptr, 16));
246 return static_cast<std::uint32_t
>(std::stoul(s,
nullptr, 10));
248 catch (
const std::exception&)
255std::vector<int> ParseSlotSpec(
const std::optional<std::string>& spec)
257 std::vector<int> result;
258 if (!spec || spec->empty())
263 std::string s = *spec;
264 std::ranges::replace(s,
',',
' ');
265 std::istringstream iss(s);
269 auto dotdot = part.find(
"..");
270 if (dotdot != std::string::npos)
272 int start = std::stoi(part.substr(0, dotdot));
273 int end = std::stoi(part.substr(dotdot + 2));
274 for (
int i = start; i <= end; ++i)
281 result.push_back(std::stoi(part));
289const std::map<std::string, int>& TypeSizeTable()
291 static const std::map<std::string, int> table = {
308 {
"TimeDifference", 4},
309 {
"F_MessageTrailer4Byte", 4},
310 {
"F_MessageTrailer5Byte", 5},
315int DataItemLength(
const std::string& dataType,
const std::optional<std::string>& lengthAttr)
317 if (dataType ==
"OctetString" || dataType ==
"VisibleString")
319 return lengthAttr ?
static_cast<int>(ParseInt(lengthAttr)) : 0;
321 auto it = TypeSizeTable().find(dataType);
322 return it != TypeSizeTable().end() ? it->second : 1;
330std::map<std::string, std::string> BuildTextTable(xmlXPathContextPtr
ctx, xmlNodePtr root)
332 std::map<std::string, std::string> table;
333 for (
auto* textNode : FindAll(
ctx, root,
"Text"))
335 auto id = GetAttrOpt(textNode,
"TextId");
336 auto value = GetAttrOpt(textNode,
"Value");
346std::string ResolveName(xmlXPathContextPtr
ctx, xmlNodePtr parent,
const std::map<std::string, std::string>& textTable,
347 const std::string& fallback)
349 xmlNodePtr nameNode = FindFirst(
ctx, parent,
"Name");
350 if (nameNode ==
nullptr)
355 if (
auto textId = GetAttrOpt(nameNode,
"TextId"))
357 auto it = textTable.find(*textId);
358 if (it != textTable.end())
363 if (
auto value = GetAttrOpt(nameNode,
"Value"))
374GSDMLSubmodule ParseSubmoduleNode(xmlXPathContextPtr
ctx, xmlNodePtr submoduleNode,
375 const std::map<std::string, std::string>& textTable)
378 sm.id = GetAttr(submoduleNode,
"ID");
379 sm.submoduleIdentNumber = ParseInt(GetAttrOpt(submoduleNode,
"SubmoduleIdentNumber"));
380 sm.name = ResolveName(
ctx, submoduleNode, textTable, sm.id);
381 sm.allowedSubslots = ParseSlotSpec(GetAttrOpt(submoduleNode,
"AllowedInSubslots"));
382 sm.fixedSubslots = ParseSlotSpec(GetAttrOpt(submoduleNode,
"FixedInSubslots"));
383 sm.usedSubslots = ParseSlotSpec(GetAttrOpt(submoduleNode,
"UsedInSubslots"));
385 xmlNodePtr ioData = FindFirst(
ctx, submoduleNode,
"IOData");
386 if (ioData !=
nullptr)
388 for (
auto* input : FindChildren(
ctx, ioData,
"Input"))
390 for (
auto* item : FindChildren(
ctx, input,
"DataItem"))
393 di.dataType = GetAttr(item,
"DataType");
394 di.textId = GetAttr(item,
"TextId");
395 di.length = DataItemLength(di.dataType, GetAttrOpt(item,
"Length"));
396 sm.inputLength += di.length;
397 sm.inputItems.push_back(di);
400 for (
auto* output : FindChildren(
ctx, ioData,
"Output"))
402 for (
auto* item : FindChildren(
ctx, output,
"DataItem"))
405 di.dataType = GetAttr(item,
"DataType");
406 di.textId = GetAttr(item,
"TextId");
407 di.length = DataItemLength(di.dataType, GetAttrOpt(item,
"Length"));
408 sm.outputLength += di.length;
409 sm.outputItems.push_back(di);
425 auto it =
modules.find(moduleId);
426 return it !=
modules.end() ? &it->second :
nullptr;
522 std::vector<rpc::IOSlot> slots;
537 auto configureIocrParticipation = [](
rpc::IOSlot& slot,
bool includeZeroIoInCyclicConfiguration =
false)
541 const bool isZeroIo = !hasInputData && !hasOutputData;
543 if (isZeroIo && includeZeroIoInCyclicConfiguration)
601 slot.
subslot =
static_cast<std::uint16_t
>(i + 1);
607 configureIocrParticipation(slot,
true);
609 slots.push_back(slot);
629 configureIocrParticipation(slot,
true);
631 slots.push_back(slot);
640 const auto inlineIt =
module.submodules.find(submoduleId);
644 return &inlineIt->second;
649 return ref.submoduleId == submoduleId;
664 auto isModuleAllowedInSlot = [&](
const std::string& moduleId,
int slotNumber)
666 bool hasRestrictions =
false;
670 if (ref.moduleId != moduleId)
675 if (!ref.allowedSlots.empty())
677 hasRestrictions =
true;
679 if (std::ranges::find(
681 slotNumber) != ref.allowedSlots.end())
687 if (std::ranges::find(
689 slotNumber) != ref.fixedSlots.end())
694 if (std::ranges::find(
696 slotNumber) != ref.usedSlots.end())
702 return !hasRestrictions;
707 auto validateSubslot = [&](
const GSDMLModule& module,
const std::string& submoduleId,
int subslotNumber)
711 return ref.submoduleId == submoduleId;
719 if (!refIt->allowedSubslots.empty() &&
720 std::ranges::find(refIt->allowedSubslots, subslotNumber) == refIt->allowedSubslots.end())
722 throw std::invalid_argument(
"Submodule '" + submoduleId +
"' is not allowed in subslot " + std::to_string(subslotNumber) +
" of module '" + module.
id +
"'");
728 auto addModuleSubmodule = [&](
int slotNumber,
const std::string& moduleId,
int subslotNumber,
const std::string& submoduleId)
732 if (module ==
nullptr)
734 throw std::invalid_argument(
"Unknown module ID: " + moduleId);
749 if (!isModuleAllowedInSlot(moduleId, slotNumber))
751 throw std::invalid_argument(
"Module '" + moduleId +
"' is not allowed in slot " + std::to_string(slotNumber));
754 validateSubslot(*module, submoduleId, subslotNumber);
756 const GSDMLSubmodule* submodule = resolveSubmodule(*module, submoduleId);
758 if (submodule ==
nullptr)
760 throw std::invalid_argument(
"Unknown submodule ID '" + submoduleId +
"' in module '" + moduleId +
"'");
793 ioSlot.
slot =
static_cast<std::uint16_t
>(slotNumber);
794 ioSlot.
subslot =
static_cast<std::uint16_t
>(subslotNumber);
800 configureIocrParticipation(ioSlot);
802 slots.push_back(ioSlot);
812 if (!plugged.empty())
814 for (
const auto& [slotNumber, moduleId, subslotNumber, submoduleId] : plugged)
910 const GSDMLModule*
module = FindModule(moduleRef.moduleId);
912 if (module ==
nullptr)
914 throw std::invalid_argument(
"Unknown referenced module ID: " + moduleRef.moduleId);
917 std::vector<int> selectedSlots;
919 if (!moduleRef.fixedSlots.empty())
921 selectedSlots = moduleRef.fixedSlots;
923 else if (!moduleRef.usedSlots.empty())
925 selectedSlots = moduleRef.usedSlots;
933 for (
const int slotNumber : selectedSlots)
941 for (
const auto& submoduleRef :
module->submoduleReferences)
943 std::vector<int> selectedSubslots;
945 if (!submoduleRef.fixedSubslots.empty())
947 selectedSubslots = submoduleRef.fixedSubslots;
949 else if (!submoduleRef.usedSubslots.empty())
951 selectedSubslots = submoduleRef.usedSubslots;
958 for (
const int subslotNumber : selectedSubslots)
964 submoduleRef.submoduleId);
975 for (
const auto& [submoduleId, submodule] :
module->submodules)
977 std::vector<int> selectedSubslots;
979 if (!submodule.fixedSubslots.empty())
981 selectedSubslots = submodule.fixedSubslots;
983 else if (!submodule.usedSubslots.empty())
985 selectedSubslots = submodule.usedSubslots;
991 selectedSubslots = {1};
994 for (
const int subslotNumber : selectedSubslots)
1014 XmlDocGuard docGuard;
1015 docGuard.doc = xmlReadMemory(xml.data(),
static_cast<int>(xml.size()),
"gsdml.xml",
nullptr,
1016 XML_PARSE_NOBLANKS);
1017 if (docGuard.doc ==
nullptr)
1019 throw std::runtime_error(
"Failed to parse GSDML: not well-formed XML");
1022 xmlNodePtr root = xmlDocGetRootElement(docGuard.doc);
1023 if (root ==
nullptr)
1025 throw std::runtime_error(
"Failed to parse GSDML: empty document");
1028 XPathContextGuard ctxGuard;
1029 ctxGuard.ctx = xmlXPathNewContext(docGuard.doc);
1030 if (ctxGuard.ctx ==
nullptr)
1032 throw std::runtime_error(
"Failed to create XPath context");
1034 xmlXPathContextPtr
ctx = ctxGuard.ctx;
1038 auto textTable = BuildTextTable(
ctx, root);
1040 xmlNodePtr deviceIdentity = FindFirst(
ctx, root,
"DeviceIdentity");
1041 if (deviceIdentity ==
nullptr)
1047 device.
vendorId =
static_cast<std::uint16_t
>(ParseInt(GetAttrOpt(deviceIdentity,
"VendorID")));
1048 device.
deviceId =
static_cast<std::uint16_t
>(ParseInt(GetAttrOpt(deviceIdentity,
"DeviceID")));
1049 device.
vendorName = ResolveName(
ctx, deviceIdentity, textTable,
"");
1051 xmlNodePtr deviceFunction = FindFirst(
ctx, root,
"DeviceFunction");
1052 device.
deviceName = (deviceFunction !=
nullptr) ? ResolveName(
ctx, deviceFunction, textTable,
"") : device.
vendorName;
1055 xmlNodePtr dap = FindFirst(
ctx, root,
"DeviceAccessPointItem");
1064 device.
dapSlots = ParseSlotSpec(GetAttrOpt(dap,
"PhysicalSlots"));
1066 xmlNodePtr sysList = FindFirst(
ctx, dap,
"SystemDefinedSubmoduleList");
1067 if (sysList !=
nullptr)
1069 for (
auto* subNode : FindChildren(
ctx, sysList,
"InterfaceSubmoduleItem"))
1074 sys.
subslot =
static_cast<std::uint16_t
>(ParseInt(GetAttrOpt(subNode,
"SubslotNumber")));
1077 for (
auto* subNode : FindChildren(
ctx, sysList,
"PortSubmoduleItem"))
1082 sys.
subslot =
static_cast<std::uint16_t
>(ParseInt(GetAttrOpt(subNode,
"SubslotNumber")));
1088 xmlNodePtr dapVsl = FindFirst(
ctx, dap,
"VirtualSubmoduleList");
1089 if (dapVsl !=
nullptr)
1097 for (
auto* subNode : FindChildren(
ctx, dapVsl,
"VirtualSubmoduleItem"))
1120 device.
modules[dapModule.
id] = std::move(dapModule);
1125 xmlNodePtr moduleList = FindFirst(
ctx, root,
"ModuleList");
1126 if (moduleList !=
nullptr)
1128 for (
auto* moduleNode : FindChildren(
ctx, moduleList,
"ModuleItem"))
1131 mod.
id = GetAttr(moduleNode,
"ID");
1133 mod.
name = ResolveName(
ctx, moduleNode, textTable, mod.
id);
1134 mod.
physicalSubslots = ParseSlotSpec(GetAttrOpt(moduleNode,
"PhysicalSubslots"));
1136 xmlNodePtr vsl = FindFirst(
ctx, moduleNode,
"VirtualSubmoduleList");
1139 for (
auto* subNode : FindChildren(
ctx, vsl,
"VirtualSubmoduleItem"))
1160 xmlNodePtr usableSubmodules = FindFirst(
ctx, moduleNode,
"UseableSubmodules");
1162 if (usableSubmodules !=
nullptr)
1164 for (
auto* refNode : FindChildren(
ctx, usableSubmodules,
"SubmoduleItemRef"))
1167 ref.
submoduleId = GetAttr(refNode,
"SubmoduleItemTarget");
1168 ref.
allowedSubslots = ParseSlotSpec(GetAttrOpt(refNode,
"AllowedInSubslots"));
1169 ref.
fixedSubslots = ParseSlotSpec(GetAttrOpt(refNode,
"FixedInSubslots"));
1170 ref.
usedSubslots = ParseSlotSpec(GetAttrOpt(refNode,
"UsedInSubslots"));
1175 device.
modules[mod.
id] = std::move(mod);
1188 xmlNodePtr submoduleList = FindFirst(
ctx, root,
"SubmoduleList");
1190 if (submoduleList !=
nullptr)
1192 for (
auto* submoduleNode :
1193 FindChildren(
ctx, submoduleList,
"SubmoduleItem"))
1196 ParseSubmoduleNode(
ctx, submoduleNode, textTable);
1198 device.
submodules[submodule.
id] = std::move(submodule);
1204 xmlNodePtr useList = FindFirst(
ctx, (dap !=
nullptr) ? dap : root,
"UseableModules");
1205 if (useList !=
nullptr)
1207 for (
auto* useNode : FindChildren(
ctx, useList,
"ModuleItemRef"))
1214 ref.
moduleId = GetAttr(useNode,
"ModuleItemTarget");
1215 ref.
allowedSlots = ParseSlotSpec(GetAttrOpt(useNode,
"AllowedInSlots"));
1216 ref.
fixedSlots = ParseSlotSpec(GetAttrOpt(useNode,
"FixedInSlots"));
1217 ref.
usedSlots = ParseSlotSpec(GetAttrOpt(useNode,
"UsedInSlots"));
1223 xmlNodePtr fixedList = FindFirst(
ctx, (dap !=
nullptr) ? dap : root,
"PredefinedPnioModules");
1224 if (fixedList !=
nullptr)
1226 for (
auto* fixedNode : FindChildren(
ctx, fixedList,
"PnioModuleItemRef"))
1229 ref.
moduleId = GetAttr(fixedNode,
"ModuleItemTarget");
1230 ref.
fixedSlots = ParseSlotSpec(GetAttrOpt(fixedNode,
"FixedInSlots"));
1246 XmlDocGuard docGuard;
1247 docGuard.doc = xmlReadFile(path.c_str(),
nullptr, XML_PARSE_NOBLANKS);
1248 if (docGuard.doc ==
nullptr)
1250 throw std::runtime_error(
"Failed to open or parse GSDML file: " + path);
1256 xmlChar* buf =
nullptr;
1258 xmlDocDumpMemory(docGuard.doc, &buf, &size);
1259 std::string xml(
reinterpret_cast<const char*
>(buf),
static_cast<std::size_t
>(size));
Declares the GSDML parser and PROFINET device-description data model.
GSDMLDevice ParseGsdmlString(const std::string &xml)
Parse GSDML content already held in memory.
GSDMLDevice ParseGsdml(const std::string &path)
Parse a GSDML file into a GSDMLDevice.
Complete parsed GSDML device profile.
std::string vendorName
Vendor name from the GSDML's DeviceIdentity.
std::map< std::string, GSDMLModule > modules
Every module declared in the GSDML's ModuleList, keyed by GSDML ID.
std::vector< GSDMLModuleReference > moduleReferences
Modules referenced by the DAP's UseableModules list.
std::vector< GSDMLSubmodule > dapSubmodules
DAP virtual submodules.
std::uint16_t vendorId
PROFINET vendor ID from the GSDML's DeviceIdentity (VendorID attribute).
std::uint32_t dapSubmoduleIdentNumber
The device access point's own (built-in) submodule ident number.
const GSDMLSubmodule * FindSubmodule(const std::string &submoduleId) const
Look up a globally defined submodule.
std::vector< GSDMLSystemSubmodule > systemSubmodules
System-defined submodules built into the DAP (e.g. interface, port 1).
std::vector< rpc::IOSlot > BuildIoSlots(const std::vector< std::tuple< int, std::string, int, std::string > > &plugged={}) const
Build I/O slot descriptions from the parsed GSDML configuration.
std::uint16_t deviceId
PROFINET device ID from the GSDML's DeviceIdentity (DeviceID attribute).
std::uint32_t dapModuleIdentNumber
The device access point's own module ident number.
std::map< std::string, GSDMLSubmodule > submodules
Every globally defined SubmoduleItem, keyed by GSDML ID.
std::string deviceName
Device name from the GSDML's DeviceIdentity / DeviceFunction.
const GSDMLModule * FindModule(const std::string &moduleId) const
Look up a module by its GSDML ID.
std::vector< int > dapSlots
Slot numbers where the DAP may be fixed/plugged (from UseableModules/PlugSlots).
std::string dapModuleId
The device access point's own (built-in) identifier to matchs with the submodule.
Reference describing how a module may be used by the DAP.
std::string moduleId
Referenced ModuleItem ID.
std::vector< int > usedSlots
Slots where the module is currently used/preselected.
std::vector< int > allowedSlots
Slots where the module is allowed.
std::vector< int > fixedSlots
Slots where the module is fixed.
A GSDML module definition (ModuleItem), possibly offering multiple virtual submodules.
std::string id
GSDML ID attribute (used to resolve ModuleItemTarget references).
std::vector< GSDMLSubmoduleReference > submoduleReferences
References to globally defined SubmoduleItem definitions.
std::map< std::string, GSDMLSubmodule > submodules
Virtual submodules this module offers, keyed by their GSDML ID.
std::string name
Human-readable module name (from the GSDML's Name/TextId, resolved via ExternalTextList).
std::vector< int > physicalSubslots
Physical subslots supported by this module.
std::uint32_t moduleIdentNumber
Module ident number (hex value from the GSDML, parsed to an integer).
Reference to a submodule that may be used by a module.
std::vector< int > usedSubslots
Subslots where this submodule is currently used/preselected.
std::vector< int > allowedSubslots
Subslots where this submodule is allowed.
std::vector< int > fixedSubslots
Subslots where this submodule is fixed.
std::string submoduleId
Referenced SubmoduleItem ID.
A GSDML virtual submodule definition (VirtualSubmoduleItem).
std::uint32_t submoduleIdentNumber
Submodule ident number (hex value from the GSDML, parsed to an integer).
std::string id
GSDML ID attribute (used to resolve VirtualSubmoduleItemTarget references).
int outputLength
Total output data length in bytes (sum of Output DataItems).
int inputLength
Total input data length in bytes (sum of Input DataItems).
A GSDML system-defined submodule (e.g. built into the DAP: interface, port).
std::uint16_t subslot
Subslot this submodule occupies.
std::uint32_t submoduleIdentNumber
Submodule ident number.
One slot/subslot's expected module configuration and IO data sizes.
std::uint16_t inputLength
Expected input data length in bytes (0 if no input data).
std::uint16_t slot
Slot number.
std::uint16_t outputLength
Expected output data length in bytes (0 if no output data).
bool outputIoData
IODataObject for Output CR.
bool inputIoData
IODataObject for Input CR.
std::uint32_t moduleIdent
Expected module ident number.
std::uint32_t submoduleIdent
Expected submodule ident number.
std::uint16_t subslot
Subslot number.