PROFINET IO Controller Stack 1.0.0
Modern C++ implementation of a PROFINET IO Controller stack
Loading...
Searching...
No Matches
gsdml.cpp
Go to the documentation of this file.
1
30
31#include "profinet/gsdml.h"
32
33#include <libxml/parser.h>
34#include <libxml/tree.h>
35#include <libxml/xpath.h>
36#include <libxml/xpathInternals.h>
37
38#include <algorithm>
39#include <map>
40#include <sstream>
41#include <stdexcept>
42
43namespace profinet::gsdml
44{
45
46namespace
47{
48
49// =============================================================================
50// libxml2 RAII wrappers
51// =============================================================================
52
53struct XmlDocGuard
54{
55 xmlDocPtr doc = nullptr;
56 ~XmlDocGuard()
57 {
58 if (doc != nullptr)
59 {
60 xmlFreeDoc(doc);
61 }
62 }
63};
64
65struct XPathContextGuard
66{
67 xmlXPathContextPtr ctx = nullptr;
68 ~XPathContextGuard()
69 {
70 if (ctx != nullptr)
71 {
72 xmlXPathFreeContext(ctx);
73 }
74 }
75};
76
77struct XPathObjectGuard
78{
79 xmlXPathObjectPtr obj = nullptr;
80 ~XPathObjectGuard()
81 {
82 if (obj != nullptr)
83 {
84 xmlXPathFreeObject(obj);
85 }
86 }
87};
88
89// =============================================================================
90// Namespace-agnostic query helpers (GSDML files declare a default
91// namespace that varies by vendor/profile version, so we match on
92// local-name() throughout, exactly like gsdml.py's _find/_findall do with
93// ElementTree's `{*}Tag` wildcard).
94// =============================================================================
95
96std::string EscapeXPathLiteral(const std::string& s)
97{
98 // XPath 1.0 has no literal-escaping, so build via concat() if the
99 // string itself contains a quote. GSDML attribute values here (IDs,
100 // TextIds) never contain quotes in practice, so this is a defensive
101 // fallback rather than a load-bearing path.
102 if (s.find('\'') == std::string::npos)
103 {
104 return "'" + s + "'";
105 }
106 std::ostringstream oss;
107 oss << "concat(";
108 bool first = true;
109 std::string cur;
110 for (char c : s)
111 {
112 if (c == '\'')
113 {
114 if (!first)
115 {
116 oss << ", ";
117 }
118 oss << "'" << cur << "'";
119 cur.clear();
120 oss << ", \"'\"";
121 first = false;
122 }
123 else
124 {
125 cur.push_back(c);
126 }
127 }
128 if (!cur.empty())
129 {
130 if (!first)
131 {
132 oss << ", ";
133 }
134 oss << "'" << cur << "'";
135 }
136 oss << ")";
137 return oss.str();
138}
139
142std::vector<xmlNodePtr> XPathFind(xmlXPathContextPtr ctx, xmlNodePtr node, const std::string& expr)
143{
144 xmlXPathContextPtr currentCtx = ctx;
145 xmlNodePtr savedNode = currentCtx->node;
146 if (node != nullptr)
147 {
148 currentCtx->node = node;
149 }
150
151 XPathObjectGuard guard;
152 guard.obj = xmlXPathEvalExpression(reinterpret_cast<const xmlChar*>(expr.c_str()), currentCtx);
153 currentCtx->node = savedNode;
154
155 std::vector<xmlNodePtr> result;
156 if ((guard.obj == nullptr) || (guard.obj->nodesetval == nullptr))
157 {
158 return result;
159 }
160 for (int i = 0; i < guard.obj->nodesetval->nodeNr; ++i)
161 {
162 result.push_back(guard.obj->nodesetval->nodeTab[i]);
163 }
164 return result;
165}
166
168xmlNodePtr FindFirst(xmlXPathContextPtr ctx, xmlNodePtr node, const std::string& tag)
169{
170 auto results = XPathFind(ctx, node, ".//*[local-name()='" + tag + "']");
171 return results.empty() ? nullptr : results.front();
172}
173
175std::vector<xmlNodePtr> FindAll(xmlXPathContextPtr ctx, xmlNodePtr node, const std::string& tag)
176{
177 return XPathFind(ctx, node, ".//*[local-name()='" + tag + "']");
178}
179
181std::vector<xmlNodePtr> FindChildren(xmlXPathContextPtr ctx, xmlNodePtr node, const std::string& tag)
182{
183 return XPathFind(ctx, node, "./*[local-name()='" + tag + "']");
184}
185
186std::string GetAttr(xmlNodePtr node, const std::string& name, const std::string& Default = "")
187{
188 if (node == nullptr)
189 {
190 return Default;
191 }
192 xmlChar* val = xmlGetProp(node, reinterpret_cast<const xmlChar*>(name.c_str()));
193 if (val == nullptr)
194 {
195 return Default;
196 }
197 std::string result(reinterpret_cast<const char*>(val));
198 xmlFree(val);
199 return result;
200}
201
202std::optional<std::string> GetAttrOpt(xmlNodePtr node, const std::string& name)
203{
204 if (node == nullptr)
205 {
206 return std::nullopt;
207 }
208 xmlChar* val = xmlGetProp(node, reinterpret_cast<const xmlChar*>(name.c_str()));
209 if (val == nullptr)
210 {
211 return std::nullopt;
212 }
213 std::string result(reinterpret_cast<const char*>(val));
214 xmlFree(val);
215 return result;
216}
217
218// =============================================================================
219// Value parsing helpers
220// =============================================================================
221
224std::uint32_t ParseInt(const std::optional<std::string>& spec)
225{
226 if (!spec)
227 {
228 return 0;
229 }
230 const std::string& s = *spec;
231 if (s.empty())
232 {
233 return 0;
234 }
235
236 if (s.size() > 3 && s[0] == '#' && s[1] == '1' && s[2] == '6' && s[3] == '#')
237 {
238 return static_cast<std::uint32_t>(std::stoul(s.substr(4), nullptr, 16));
239 }
240 if (s.size() > 2 && s[0] == '0' && (s[1] == 'x' || s[1] == 'X'))
241 {
242 return static_cast<std::uint32_t>(std::stoul(s.substr(2), nullptr, 16));
243 }
244 try
245 {
246 return static_cast<std::uint32_t>(std::stoul(s, nullptr, 10));
247 }
248 catch (const std::exception&)
249 {
250 return 0;
251 }
252}
253
255std::vector<int> ParseSlotSpec(const std::optional<std::string>& spec)
256{
257 std::vector<int> result;
258 if (!spec || spec->empty())
259 {
260 return result;
261 }
262
263 std::string s = *spec;
264 std::ranges::replace(s, ',', ' ');
265 std::istringstream iss(s);
266 std::string part;
267 while (iss >> part)
268 {
269 auto dotdot = part.find("..");
270 if (dotdot != std::string::npos)
271 {
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)
275 {
276 result.push_back(i);
277 }
278 }
279 else
280 {
281 result.push_back(std::stoi(part));
282 }
283 }
284 return result;
285}
286
289const std::map<std::string, int>& TypeSizeTable()
290{
291 static const std::map<std::string, int> table = {
292 {"Bit", 1},
293 {"BitArea", 1},
294 {"Boolean", 1},
295 {"Integer8", 1},
296 {"Unsigned8", 1},
297 {"Integer16", 2},
298 {"Unsigned16", 2},
299 {"Integer32", 4},
300 {"Unsigned32", 4},
301 {"Integer64", 8},
302 {"Unsigned64", 8},
303 {"Float32", 4},
304 {"Float64", 8},
305 {"Date", 2},
306 {"Time", 4},
307 {"TimeOfDay", 4},
308 {"TimeDifference", 4},
309 {"F_MessageTrailer4Byte", 4},
310 {"F_MessageTrailer5Byte", 5},
311 };
312 return table;
313}
314
315int DataItemLength(const std::string& dataType, const std::optional<std::string>& lengthAttr)
316{
317 if (dataType == "OctetString" || dataType == "VisibleString")
318 {
319 return lengthAttr ? static_cast<int>(ParseInt(lengthAttr)) : 0;
320 }
321 auto it = TypeSizeTable().find(dataType);
322 return it != TypeSizeTable().end() ? it->second : 1;
323}
324
325// =============================================================================
326// ExternalTextList resolution
327// =============================================================================
328
330std::map<std::string, std::string> BuildTextTable(xmlXPathContextPtr ctx, xmlNodePtr root)
331{
332 std::map<std::string, std::string> table;
333 for (auto* textNode : FindAll(ctx, root, "Text"))
334 {
335 auto id = GetAttrOpt(textNode, "TextId");
336 auto value = GetAttrOpt(textNode, "Value");
337 if (id && value)
338 {
339 table[*id] = *value;
340 }
341 }
342 return table;
343}
344
346std::string ResolveName(xmlXPathContextPtr ctx, xmlNodePtr parent, const std::map<std::string, std::string>& textTable,
347 const std::string& fallback)
348{
349 xmlNodePtr nameNode = FindFirst(ctx, parent, "Name");
350 if (nameNode == nullptr)
351 {
352 return fallback;
353 }
354
355 if (auto textId = GetAttrOpt(nameNode, "TextId"))
356 {
357 auto it = textTable.find(*textId);
358 if (it != textTable.end())
359 {
360 return it->second;
361 }
362 }
363 if (auto value = GetAttrOpt(nameNode, "Value"))
364 {
365 return *value;
366 }
367 return fallback;
368}
369
370// =============================================================================
371// Submodule / module parsing
372// =============================================================================
373
374GSDMLSubmodule ParseSubmoduleNode(xmlXPathContextPtr ctx, xmlNodePtr submoduleNode,
375 const std::map<std::string, std::string>& textTable)
376{
377 GSDMLSubmodule sm;
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"));
384
385 xmlNodePtr ioData = FindFirst(ctx, submoduleNode, "IOData");
386 if (ioData != nullptr)
387 {
388 for (auto* input : FindChildren(ctx, ioData, "Input"))
389 {
390 for (auto* item : FindChildren(ctx, input, "DataItem"))
391 {
392 GSDMLDataItem di;
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);
398 }
399 }
400 for (auto* output : FindChildren(ctx, ioData, "Output"))
401 {
402 for (auto* item : FindChildren(ctx, output, "DataItem"))
403 {
404 GSDMLDataItem di;
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);
410 }
411 }
412 }
413
414 return sm;
415}
416
417} // namespace
418
419// =============================================================================
420// GSDMLDevice
421// =============================================================================
422
423const GSDMLModule* GSDMLDevice::FindModule(const std::string& moduleId) const
424{
425 auto it = modules.find(moduleId);
426 return it != modules.end() ? &it->second : nullptr;
427}
428const GSDMLSubmodule* GSDMLDevice::FindSubmodule(const std::string& submoduleId) const
429{
430 const auto it = submodules.find(submoduleId);
431
432 return it != submodules.end()
433 ? &it->second
434 : nullptr;
435}
436/*
437std::vector<rpc::IOSlot> GSDMLDevice::BuildIoSlots(
438 const std::vector<std::tuple<int, std::string, int, std::string>>& plugged) const
439{
440 std::vector<rpc::IOSlot> slots;
441
442 // System-defined submodules (interface, port) always occupy their
443 // configured subslots on the DAP's slot (slot 0 by convention).
444 for (const auto& sys : systemSubmodules)
445 {
446 rpc::IOSlot slot;
447 slot.slot = 0;
448 slot.subslot = sys.subslot;
449 slot.inputLength = 0;
450 slot.outputLength = 0;
451 slot.moduleIdent = dapModuleIdentNumber;
452 slot.submoduleIdent = sys.submoduleIdentNumber;
453 slots.push_back(slot);
454 }
455
456 auto addModuleSubmodule = [&](int slot, const std::string& moduleId, int subslot,
457 const std::string& submoduleId)
458 {
459 const GSDMLModule* module = FindModule(moduleId);
460 if (!module)
461 {
462 throw std::invalid_argument("Unknown module ID: " + moduleId);
463 }
464
465 auto allowedIt = allowedSlots.find(moduleId);
466 if (allowedIt != allowedSlots.end() && !allowedIt->second.empty())
467 {
468 if (std::find(allowedIt->second.begin(), allowedIt->second.end(), slot) ==
469 allowedIt->second.end())
470 {
471 throw std::invalid_argument("Module '" + moduleId + "' is not allowed in slot " +
472 std::to_string(slot));
473 }
474 }
475
476 auto subIt = module->submodules.find(submoduleId);
477 if (subIt == module->submodules.end())
478 {
479 throw std::invalid_argument("Unknown submodule ID '" + submoduleId + "' in module '" + moduleId + "'");
480 }
481 const GSDMLSubmodule& sm = subIt->second;
482
483 rpc::IOSlot ioSlot;
484 ioSlot.slot = static_cast<std::uint16_t>(slot);
485 ioSlot.subslot = static_cast<std::uint16_t>(subslot);
486 ioSlot.inputLength = static_cast<std::uint16_t>(sm.inputLength);
487 ioSlot.outputLength = static_cast<std::uint16_t>(sm.outputLength);
488 ioSlot.moduleIdent = module->moduleIdentNumber;
489 ioSlot.submoduleIdent = sm.submoduleIdentNumber;
490 slots.push_back(ioSlot);
491 };
492
493 if (!plugged.empty())
494 {
495 for (const auto& [Slot, ModuleId, Subslot, SubmoduleId] : plugged)
496 {
497 addModuleSubmodule(Slot, ModuleId, Subslot, SubmoduleId);
498 }
499 }
500 else
501 {
502 // No explicit plan given -- fall back to whatever the GSDML marks
503 // as fixed (pre-plugged) modules, using each module's first
504 // declared submodule at subslot 1 (matches gsdml.py's default).
505 for (const auto& [slot, moduleId] : fixedModules)
506 {
507 const GSDMLModule* module = FindModule(moduleId);
508 if ((module == nullptr) || module->submodules.empty())
509 {
510 continue;
511 }
512 addModuleSubmodule(slot, moduleId, 1, module->submodules.begin()->first);
513 }
514 }
515
516 return slots;
517}
518*/
519
520std::vector<rpc::IOSlot> GSDMLDevice::BuildIoSlots(const std::vector<std::tuple<int, std::string, int, std::string>>& plugged) const
521{
522 std::vector<rpc::IOSlot> slots;
523
524 // ========================================================================
525 // Configure IOCR participation.
526 // ========================================================================
527 //
528 // A slot with input data participates as an IODataObject in the INPUT CR.
529 // A slot with output data participates as an IODataObject in the OUTPUT CR.
530 //
531 // The opposite CR carries the corresponding IOCS object.
532 //
533 // Zero-length DAP/PDEV submodules are special: they do not have process
534 // data, but they still participate in both IOCRs as required by the
535 // configured device layout.
536 //
537 auto configureIocrParticipation = [](rpc::IOSlot& slot, bool includeZeroIoInCyclicConfiguration = false)
538 {
539 const bool hasInputData = slot.inputLength > 0;
540 const bool hasOutputData = slot.outputLength > 0;
541 const bool isZeroIo = !hasInputData && !hasOutputData;
542
543 if (isZeroIo && includeZeroIoInCyclicConfiguration)
544 {
545 // DAP/PDEV cyclic objects:
546 //
547 // INPUT CR -> IOData
548 // OUTPUT CR -> IOCS
549 //
550 slot.inputIoData = true;
551 slot.outputIoData = false;
552
553 slot.inputIocs = false;
554 slot.outputIocs = true;
555
556 return;
557 }
558
559 // Normal process-data modules.
560 //
561 // Input-producing module:
562 //
563 // INPUT CR -> IOData
564 // OUTPUT CR -> IOCS
565 //
566 slot.inputIoData = hasInputData;
567 slot.outputIocs = hasInputData;
568
569 // Output-producing module:
570 //
571 // OUTPUT CR -> IOData
572 // INPUT CR -> IOCS
573 //
574 slot.outputIoData = hasOutputData;
575 slot.inputIocs = hasOutputData;
576 };
577
578 // ========================================================================
579 // DAP virtual submodules
580 // ========================================================================
581 //
582 // Python:
583 //
584 // for i, sub in enumerate(dap.submodules):
585 // IOSlot(
586 // slot=0,
587 // subslot=i + 1,
588 // ...
589 // )
590 //
591 // DAP virtual submodules are different from the system-defined interface
592 // and port submodules. Their subslot numbers are assigned sequentially
593 // starting at 1.
594 //
595 for (std::size_t i = 0; i < dapSubmodules.size(); ++i)
596 {
597 const GSDMLSubmodule& sub = dapSubmodules[i];
598
599 rpc::IOSlot slot;
600 slot.slot = 0;
601 slot.subslot = static_cast<std::uint16_t>(i + 1);
602 slot.inputLength = static_cast<std::uint16_t>(sub.inputLength);
603 slot.outputLength = static_cast<std::uint16_t>(sub.outputLength);
606
607 configureIocrParticipation(slot, true);
608
609 slots.push_back(slot);
610 }
611
612 // ========================================================================
613 // DAP system-defined submodules
614 // ========================================================================
615 //
616 // Interface and port submodules retain the subslot numbers specified by
617 // the GSDML. They do not carry cyclic I/O data.
618 //
619 for (const GSDMLSystemSubmodule& sys : systemSubmodules)
620 {
621 rpc::IOSlot slot;
622 slot.slot = 0;
623 slot.subslot = sys.subslot;
624 slot.inputLength = 0;
625 slot.outputLength = 0;
627 slot.submoduleIdent = sys.submoduleIdentNumber;
628
629 configureIocrParticipation(slot, true);
630
631 slots.push_back(slot);
632 }
633
634 // ========================================================================
635 // Helper for first resolve a submodule
636 // ========================================================================
637 auto resolveSubmodule = [&](const GSDMLModule& module, const std::string& submoduleId) -> const GSDMLSubmodule*
638 {
639 // Inline submodule.
640 const auto inlineIt = module.submodules.find(submoduleId);
641
642 if (inlineIt != module.submodules.end())
643 {
644 return &inlineIt->second;
645 }
646
647 const auto refIt = std::ranges::find_if(module.submoduleReferences, [&](const GSDMLSubmoduleReference& ref)
648 {
649 return ref.submoduleId == submoduleId;
650 });
651
652 if (refIt == module.submoduleReferences.end())
653 {
654 return nullptr;
655 }
656
657 // Globally defined submodule.
658 return FindSubmodule(submoduleId);
659 };
660
661 // ========================================================================
662 // Helper for validating module in slots
663 // ========================================================================
664 auto isModuleAllowedInSlot = [&](const std::string& moduleId, int slotNumber)
665 {
666 bool hasRestrictions = false;
667
668 for (const auto& ref : moduleReferences)
669 {
670 if (ref.moduleId != moduleId)
671 {
672 continue;
673 }
674
675 if (!ref.allowedSlots.empty())
676 {
677 hasRestrictions = true;
678
679 if (std::ranges::find(
680 ref.allowedSlots,
681 slotNumber) != ref.allowedSlots.end())
682 {
683 return true;
684 }
685 }
686
687 if (std::ranges::find(
688 ref.fixedSlots,
689 slotNumber) != ref.fixedSlots.end())
690 {
691 return true;
692 }
693
694 if (std::ranges::find(
695 ref.usedSlots,
696 slotNumber) != ref.usedSlots.end())
697 {
698 return true;
699 }
700 }
701
702 return !hasRestrictions;
703 };
704 // ========================================================================
705 // Helper for valid subslot
706 // ========================================================================
707 auto validateSubslot = [&](const GSDMLModule& module, const std::string& submoduleId, int subslotNumber)
708 {
709 const auto refIt = std::ranges::find_if(module.submoduleReferences, [&](const GSDMLSubmoduleReference& ref)
710 {
711 return ref.submoduleId == submoduleId;
712 });
713
714 if (refIt == module.submoduleReferences.end())
715 {
716 return;
717 }
718
719 if (!refIt->allowedSubslots.empty() &&
720 std::ranges::find(refIt->allowedSubslots, subslotNumber) == refIt->allowedSubslots.end())
721 {
722 throw std::invalid_argument("Submodule '" + submoduleId + "' is not allowed in subslot " + std::to_string(subslotNumber) + " of module '" + module.id + "'");
723 }
724 };
725 // ========================================================================
726 // Helper for adding one module submodule
727 // ========================================================================
728 auto addModuleSubmodule = [&](int slotNumber, const std::string& moduleId, int subslotNumber, const std::string& submoduleId)
729 {
730 const GSDMLModule* module = FindModule(moduleId);
731
732 if (module == nullptr)
733 {
734 throw std::invalid_argument("Unknown module ID: " + moduleId);
735 }
736 /*
737 const GSDMLModuleReference* moduleRef = nullptr;
738
739 for (const auto& ref : moduleReferences)
740 {
741 if (ref.moduleId == moduleId)
742 {
743 moduleRef = &ref;
744 break;
745 }
746 }
747 */
748 // if (moduleRef != nullptr && !moduleRef->allowedSlots.empty() && std::ranges::find(moduleRef->allowedSlots, slotNumber) == moduleRef->allowedSlots.end())
749 if (!isModuleAllowedInSlot(moduleId, slotNumber))
750 {
751 throw std::invalid_argument("Module '" + moduleId + "' is not allowed in slot " + std::to_string(slotNumber));
752 }
753
754 validateSubslot(*module, submoduleId, subslotNumber);
755
756 const GSDMLSubmodule* submodule = resolveSubmodule(*module, submoduleId);
757
758 if (submodule == nullptr)
759 {
760 throw std::invalid_argument("Unknown submodule ID '" + submoduleId + "' in module '" + moduleId + "'");
761 }
762 /*
763 // Validate the requested slot against AllowedInSlots / UseableModules.
764 const auto allowedIt = allowedSlots.find(moduleId);
765
766 if (allowedIt != allowedSlots.end() && !allowedIt->second.empty())
767 {
768 const auto slotIt =
769 std::find(allowedIt->second.begin(),
770 allowedIt->second.end(),
771 slotNumber);
772
773 if (slotIt == allowedIt->second.end())
774 {
775 throw std::invalid_argument(
776 "Module '" + moduleId + "' is not allowed in slot " +
777 std::to_string(slotNumber));
778 }
779 }
780
781 const auto submoduleIt = module->submodules.find(submoduleId);
782
783 if (submoduleIt == module->submodules.end())
784 {
785 throw std::invalid_argument(
786 "Unknown submodule ID '" + submoduleId +
787 "' in module '" + moduleId + "'");
788 }
789
790 const GSDMLSubmodule& submodule = submoduleIt->second;
791*/
792 rpc::IOSlot ioSlot;
793 ioSlot.slot = static_cast<std::uint16_t>(slotNumber);
794 ioSlot.subslot = static_cast<std::uint16_t>(subslotNumber);
795 ioSlot.inputLength = static_cast<std::uint16_t>(submodule->inputLength);
796 ioSlot.outputLength = static_cast<std::uint16_t>(submodule->outputLength);
797 ioSlot.moduleIdent = module->moduleIdentNumber;
798 ioSlot.submoduleIdent = submodule->submoduleIdentNumber;
799
800 configureIocrParticipation(ioSlot);
801
802 slots.push_back(ioSlot);
803 };
804
805 // ========================================================================
806 // Explicit module/submodule assignment
807 // ========================================================================
808 //
809 // This corresponds to the Python implementation receiving an explicit
810 // slot_assignment/submodule_assignment.
811 //
812 if (!plugged.empty())
813 {
814 for (const auto& [slotNumber, moduleId, subslotNumber, submoduleId] : plugged)
815 {
816 addModuleSubmodule(
817 slotNumber,
818 moduleId,
819 subslotNumber,
820 submoduleId);
821 }
822 return slots;
823 }
824 /*
825 // ========================================================================
826 // Default: use fixed modules from the GSDML
827 // ========================================================================
828 //
829 // Python:
830 //
831 // if slot_assignment is None:
832 // assignment = {}
833 // for mod_id, fixed in dap.fixed_slots.items():
834 // for slot_num in fixed:
835 // assignment[slot_num] = mod_id
836 //
837 // `fixedModules` currently represents:
838 //
839 // slot number -> module ID
840 //
841 for (const auto& [slotNumber, moduleId] : fixedModules)
842 {
843 const GSDMLModule* module = FindModule(moduleId);
844
845 if (module == nullptr)
846 {
847 throw std::invalid_argument(
848 "Unknown fixed module ID: " + moduleId);
849 }
850
851 // ----------------------------------------------------------------------
852 // Module with inline VirtualSubmoduleList
853 // ----------------------------------------------------------------------
854 //
855 // Python:
856 //
857 // if mod.submodules:
858 // for i, sub in enumerate(mod.submodules):
859 // IOSlot(
860 // slot=slot_num,
861 // subslot=i + 1,
862 // ...
863 // )
864 //
865 // Therefore all inline submodules must be added, not just the first one.
866 //
867 if (!module->submodules.empty())
868 {
869 std::uint16_t subslotNumber = 1;
870
871 for (const auto& [submoduleId, submodule] : module->submodules)
872 {
873 (void)submodule;
874
875 addModuleSubmodule(
876 slotNumber,
877 moduleId,
878 subslotNumber,
879 submoduleId);
880
881 ++subslotNumber;
882 }
883 }
884
885 // ----------------------------------------------------------------------
886 // Module with referenced UseableSubmodules
887 // ----------------------------------------------------------------------
888 //
889 // The Python implementation handles these through:
890 //
891 // self._add_useable_submodule_slots(...)
892 //
893 // The current C++ GSDMLModule representation does not yet contain the
894 // equivalent useable_submodules/submodule catalog information, so there
895 // is nothing further to add here.
896 //
897 // Once that representation exists, this branch should resolve the
898 // selected submodules in the same way as the Python implementation.
899
900 }
901 return slots;
902 */
903
904 // ========================================================================
905 // Default configuration from GSDML
906 // ========================================================================
907
908 for (const GSDMLModuleReference& moduleRef : moduleReferences)
909 {
910 const GSDMLModule* module = FindModule(moduleRef.moduleId);
911
912 if (module == nullptr)
913 {
914 throw std::invalid_argument("Unknown referenced module ID: " + moduleRef.moduleId);
915 }
916
917 std::vector<int> selectedSlots;
918
919 if (!moduleRef.fixedSlots.empty())
920 {
921 selectedSlots = moduleRef.fixedSlots;
922 }
923 else if (!moduleRef.usedSlots.empty())
924 {
925 selectedSlots = moduleRef.usedSlots;
926 }
927 else
928 {
929 // AllowedInSlots only describes capability, not actual selection.
930 continue;
931 }
932
933 for (const int slotNumber : selectedSlots)
934 {
935 // ================================================================
936 // Referenced SubmoduleItem definitions
937 // ================================================================
938
939 if (!module->submoduleReferences.empty())
940 {
941 for (const auto& submoduleRef : module->submoduleReferences)
942 {
943 std::vector<int> selectedSubslots;
944
945 if (!submoduleRef.fixedSubslots.empty())
946 {
947 selectedSubslots = submoduleRef.fixedSubslots;
948 }
949 else if (!submoduleRef.usedSubslots.empty())
950 {
951 selectedSubslots = submoduleRef.usedSubslots;
952 }
953 else
954 {
955 continue;
956 }
957
958 for (const int subslotNumber : selectedSubslots)
959 {
960 addModuleSubmodule(
961 slotNumber,
962 moduleRef.moduleId,
963 subslotNumber,
964 submoduleRef.submoduleId);
965 }
966 }
967
968 continue;
969 }
970
971 // ================================================================
972 // Inline VirtualSubmoduleItem definitions
973 // ================================================================
974
975 for (const auto& [submoduleId, submodule] : module->submodules)
976 {
977 std::vector<int> selectedSubslots;
978
979 if (!submodule.fixedSubslots.empty())
980 {
981 selectedSubslots = submodule.fixedSubslots;
982 }
983 else if (!submodule.usedSubslots.empty())
984 {
985 selectedSubslots = submodule.usedSubslots;
986 }
987 else if (module->submodules.size() == 1U)
988 {
989 // Compatibility fallback for simple GSDML files where a
990 // single virtual submodule has no explicit placement.
991 selectedSubslots = {1};
992 }
993
994 for (const int subslotNumber : selectedSubslots)
995 {
996 addModuleSubmodule(
997 slotNumber,
998 moduleRef.moduleId,
999 subslotNumber,
1000 submoduleId);
1001 }
1002 }
1003 }
1004 }
1005 return slots;
1006}
1007
1008// =============================================================================
1009// Parsing
1010// =============================================================================
1011
1012GSDMLDevice ParseGsdmlString(const std::string& xml)
1013{
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)
1018 {
1019 throw std::runtime_error("Failed to parse GSDML: not well-formed XML");
1020 }
1021
1022 xmlNodePtr root = xmlDocGetRootElement(docGuard.doc);
1023 if (root == nullptr)
1024 {
1025 throw std::runtime_error("Failed to parse GSDML: empty document");
1026 }
1027
1028 XPathContextGuard ctxGuard;
1029 ctxGuard.ctx = xmlXPathNewContext(docGuard.doc);
1030 if (ctxGuard.ctx == nullptr)
1031 {
1032 throw std::runtime_error("Failed to create XPath context");
1033 }
1034 xmlXPathContextPtr ctx = ctxGuard.ctx;
1035
1036 GSDMLDevice device;
1037
1038 auto textTable = BuildTextTable(ctx, root);
1039
1040 xmlNodePtr deviceIdentity = FindFirst(ctx, root, "DeviceIdentity");
1041 if (deviceIdentity == nullptr)
1042 {
1043 // throw std::runtime_error("Failed to parse GSDML: no DeviceIdentity element found");
1044 }
1045 else
1046 {
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, "");
1050 }
1051 xmlNodePtr deviceFunction = FindFirst(ctx, root, "DeviceFunction");
1052 device.deviceName = (deviceFunction != nullptr) ? ResolveName(ctx, deviceFunction, textTable, "") : device.vendorName;
1053
1054 // Device Access Point (DAP): the device's own built-in module/submodule.
1055 xmlNodePtr dap = FindFirst(ctx, root, "DeviceAccessPointItem");
1056 if (dap != nullptr)
1057 {
1058 device.dapModuleId = GetAttr(dap, "ID");
1059 device.dapModuleIdentNumber = ParseInt(GetAttrOpt(dap, "ModuleIdentNumber"));
1060
1061 // xmlNodePtr dapSubmodule = dap; // DAP's own submodule ident is on the DAP item itself.
1062 // device.dapSubmoduleIdentNumber = ParseInt(GetAttrOpt(dapSubmodule, "SubmoduleIdentNumber"));
1063
1064 device.dapSlots = ParseSlotSpec(GetAttrOpt(dap, "PhysicalSlots"));
1065
1066 xmlNodePtr sysList = FindFirst(ctx, dap, "SystemDefinedSubmoduleList");
1067 if (sysList != nullptr)
1068 {
1069 for (auto* subNode : FindChildren(ctx, sysList, "InterfaceSubmoduleItem"))
1070 {
1072 sys.submoduleIdentNumber = ParseInt(GetAttrOpt(subNode, "SubmoduleIdentNumber"));
1073 // sys.subslot = static_cast<std::uint16_t>(ParseInt(GetAttrOpt(subNode, "Subslot")));
1074 sys.subslot = static_cast<std::uint16_t>(ParseInt(GetAttrOpt(subNode, "SubslotNumber")));
1075 device.systemSubmodules.push_back(sys);
1076 }
1077 for (auto* subNode : FindChildren(ctx, sysList, "PortSubmoduleItem"))
1078 {
1080 sys.submoduleIdentNumber = ParseInt(GetAttrOpt(subNode, "SubmoduleIdentNumber"));
1081 // sys.subslot = static_cast<std::uint16_t>(ParseInt(GetAttrOpt(subNode, "Subslot")));
1082 sys.subslot = static_cast<std::uint16_t>(ParseInt(GetAttrOpt(subNode, "SubslotNumber")));
1083 device.systemSubmodules.push_back(sys);
1084 }
1085 }
1086
1087 // Modules the DAP itself provides directly (VirtualSubmoduleList on the DAP).
1088 xmlNodePtr dapVsl = FindFirst(ctx, dap, "VirtualSubmoduleList");
1089 if (dapVsl != nullptr)
1090 {
1091 // Also expose the DAP virtual submodule through the module catalogue.
1092 GSDMLModule dapModule;
1093 dapModule.id = device.dapModuleId;
1094 dapModule.moduleIdentNumber = device.dapModuleIdentNumber;
1095 dapModule.name = device.deviceName;
1096
1097 for (auto* subNode : FindChildren(ctx, dapVsl, "VirtualSubmoduleItem"))
1098 {
1099 GSDMLSubmodule sm = ParseSubmoduleNode(ctx, subNode, textTable);
1100
1101 // Keep DAP virtual submodules separately so BuildIoSlots() can reproduce
1102 // the Python implementation's DAP slot-0 entries.
1103 device.dapSubmodules.push_back(sm);
1104
1105 if (device.dapModuleId == sm.id)
1106 {
1108 }
1109 dapModule.submodules[sm.id.empty() ? "dap" : sm.id] = sm;
1110 }
1111 // if (sm.submoduleIdentNumber == 0)
1112 // {
1113 // // hier moet een fix
1114 // sm.submoduleIdentNumber = device.dapSubmoduleIdentNumber;
1115 // }
1116 if (device.dapSubmoduleIdentNumber == 0 && !device.dapSubmodules.empty())
1117 {
1118 device.dapSubmoduleIdentNumber = device.dapSubmodules.front().submoduleIdentNumber;
1119 }
1120 device.modules[dapModule.id] = std::move(dapModule);
1121 }
1122 }
1123
1124 // ModuleList: every pluggable module and its virtual submodules.
1125 xmlNodePtr moduleList = FindFirst(ctx, root, "ModuleList");
1126 if (moduleList != nullptr)
1127 {
1128 for (auto* moduleNode : FindChildren(ctx, moduleList, "ModuleItem"))
1129 {
1130 GSDMLModule mod;
1131 mod.id = GetAttr(moduleNode, "ID");
1132 mod.moduleIdentNumber = ParseInt(GetAttrOpt(moduleNode, "ModuleIdentNumber"));
1133 mod.name = ResolveName(ctx, moduleNode, textTable, mod.id);
1134 mod.physicalSubslots = ParseSlotSpec(GetAttrOpt(moduleNode, "PhysicalSubslots"));
1135
1136 xmlNodePtr vsl = FindFirst(ctx, moduleNode, "VirtualSubmoduleList");
1137 if (vsl != nullptr)
1138 {
1139 for (auto* subNode : FindChildren(ctx, vsl, "VirtualSubmoduleItem"))
1140 {
1141 GSDMLSubmodule sm = ParseSubmoduleNode(ctx, subNode, textTable);
1142 mod.submodules[sm.id] = std::move(sm);
1143 }
1144 }
1145
1146 // ----------------------------------------------------------------------
1147 // Module with referenced UseableSubmodules
1148 // ----------------------------------------------------------------------
1149 //
1150 // The Python implementation handles these through:
1151 //
1152 // self._add_useable_submodule_slots(...)
1153 //
1154 // The current C++ GSDMLModule representation does not yet contain the
1155 // equivalent useable_submodules/submodule catalog information, so there
1156 // is nothing further to add here.
1157 //
1158 // Once that representation exists, this branch should resolve the
1159 // selected submodules in the same way as the Python implementation.
1160 xmlNodePtr usableSubmodules = FindFirst(ctx, moduleNode, "UseableSubmodules");
1161
1162 if (usableSubmodules != nullptr)
1163 {
1164 for (auto* refNode : FindChildren(ctx, usableSubmodules, "SubmoduleItemRef"))
1165 {
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"));
1171 mod.submoduleReferences.push_back(std::move(ref));
1172 }
1173 }
1174
1175 device.modules[mod.id] = std::move(mod);
1176 }
1177 }
1178
1179 // ========================================================================
1180 // Global SubmoduleList
1181 // ========================================================================
1182 //
1183 // Some GSDML files define submodules inline inside a ModuleItem using
1184 // VirtualSubmoduleList. Others, such as Phoenix Contact GSDMLs, define
1185 // SubmoduleItem objects globally and reference them through
1186 // UseableSubmodules/SubmoduleItemRef.
1187 //
1188 xmlNodePtr submoduleList = FindFirst(ctx, root, "SubmoduleList");
1189
1190 if (submoduleList != nullptr)
1191 {
1192 for (auto* submoduleNode :
1193 FindChildren(ctx, submoduleList, "SubmoduleItem"))
1194 {
1195 GSDMLSubmodule submodule =
1196 ParseSubmoduleNode(ctx, submoduleNode, textTable);
1197
1198 device.submodules[submodule.id] = std::move(submodule);
1199 }
1200 }
1201
1202 // UseableModules: which slots each module may be plugged into, and
1203 // which modules are fixed (pre-plugged) at a given slot.
1204 xmlNodePtr useList = FindFirst(ctx, (dap != nullptr) ? dap : root, "UseableModules");
1205 if (useList != nullptr)
1206 {
1207 for (auto* useNode : FindChildren(ctx, useList, "ModuleItemRef"))
1208 {
1209 /*
1210 std::string moduleId = GetAttr(useNode, "ModuleItemTarget");
1211 device.allowedSlots[moduleId] = ParseSlotSpec(GetAttrOpt(useNode, "AllowedInSlots"));
1212*/
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"));
1218 device.moduleReferences.push_back(std::move(ref));
1219 }
1220 }
1221 // PredefinedPnioModules:
1222 //
1223 xmlNodePtr fixedList = FindFirst(ctx, (dap != nullptr) ? dap : root, "PredefinedPnioModules");
1224 if (fixedList != nullptr)
1225 {
1226 for (auto* fixedNode : FindChildren(ctx, fixedList, "PnioModuleItemRef"))
1227 {
1229 ref.moduleId = GetAttr(fixedNode, "ModuleItemTarget");
1230 ref.fixedSlots = ParseSlotSpec(GetAttrOpt(fixedNode, "FixedInSlots"));
1231 device.moduleReferences.push_back(std::move(ref));
1232
1233 /*
1234 for (int slot : ParseSlotSpec(GetAttrOpt(fixedNode, "FixedInSlots")))
1235 {
1236 device.fixedModules.emplace_back(slot, moduleId);
1237 }
1238 */
1239 }
1240 }
1241 return device;
1242}
1243
1244GSDMLDevice ParseGsdml(const std::string& path)
1245{
1246 XmlDocGuard docGuard;
1247 docGuard.doc = xmlReadFile(path.c_str(), nullptr, XML_PARSE_NOBLANKS);
1248 if (docGuard.doc == nullptr)
1249 {
1250 throw std::runtime_error("Failed to open or parse GSDML file: " + path);
1251 }
1252
1253 // Re-serialize and re-parse via ParseGsdmlString to keep a single
1254 // parsing code path (xmlReadFile already validated well-formedness,
1255 // so this second pass is cheap and keeps the implementation simple).
1256 xmlChar* buf = nullptr;
1257 int size = 0;
1258 xmlDocDumpMemory(docGuard.doc, &buf, &size);
1259 std::string xml(reinterpret_cast<const char*>(buf), static_cast<std::size_t>(size));
1260 xmlFree(buf);
1261
1262 return ParseGsdmlString(xml);
1263}
1264
1265} // namespace profinet::gsdml
xmlXPathObjectPtr obj
Definition gsdml.cpp:79
xmlXPathContextPtr ctx
Definition gsdml.cpp:67
xmlDocPtr doc
Definition gsdml.cpp:55
Declares the GSDML parser and PROFINET device-description data model.
GSDMLDevice ParseGsdmlString(const std::string &xml)
Parse GSDML content already held in memory.
Definition gsdml.cpp:1012
GSDMLDevice ParseGsdml(const std::string &path)
Parse a GSDML file into a GSDMLDevice.
Definition gsdml.cpp:1244
Complete parsed GSDML device profile.
Definition gsdml.h:140
std::string vendorName
Vendor name from the GSDML's DeviceIdentity.
Definition gsdml.h:142
std::map< std::string, GSDMLModule > modules
Every module declared in the GSDML's ModuleList, keyed by GSDML ID.
Definition gsdml.h:166
std::vector< GSDMLModuleReference > moduleReferences
Modules referenced by the DAP's UseableModules list.
Definition gsdml.h:172
std::vector< GSDMLSubmodule > dapSubmodules
DAP virtual submodules.
Definition gsdml.h:160
std::uint16_t vendorId
PROFINET vendor ID from the GSDML's DeviceIdentity (VendorID attribute).
Definition gsdml.h:146
std::uint32_t dapSubmoduleIdentNumber
The device access point's own (built-in) submodule ident number.
Definition gsdml.h:153
const GSDMLSubmodule * FindSubmodule(const std::string &submoduleId) const
Look up a globally defined submodule.
Definition gsdml.cpp:428
std::vector< GSDMLSystemSubmodule > systemSubmodules
System-defined submodules built into the DAP (e.g. interface, port 1).
Definition gsdml.h:163
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.
Definition gsdml.cpp:520
std::uint16_t deviceId
PROFINET device ID from the GSDML's DeviceIdentity (DeviceID attribute).
Definition gsdml.h:148
std::uint32_t dapModuleIdentNumber
The device access point's own module ident number.
Definition gsdml.h:155
std::map< std::string, GSDMLSubmodule > submodules
Every globally defined SubmoduleItem, keyed by GSDML ID.
Definition gsdml.h:169
std::string deviceName
Device name from the GSDML's DeviceIdentity / DeviceFunction.
Definition gsdml.h:144
const GSDMLModule * FindModule(const std::string &moduleId) const
Look up a module by its GSDML ID.
Definition gsdml.cpp:423
std::vector< int > dapSlots
Slot numbers where the DAP may be fixed/plugged (from UseableModules/PlugSlots).
Definition gsdml.h:157
std::string dapModuleId
The device access point's own (built-in) identifier to matchs with the submodule.
Definition gsdml.h:151
Reference describing how a module may be used by the DAP.
Definition gsdml.h:124
std::string moduleId
Referenced ModuleItem ID.
Definition gsdml.h:126
std::vector< int > usedSlots
Slots where the module is currently used/preselected.
Definition gsdml.h:135
std::vector< int > allowedSlots
Slots where the module is allowed.
Definition gsdml.h:129
std::vector< int > fixedSlots
Slots where the module is fixed.
Definition gsdml.h:132
A GSDML module definition (ModuleItem), possibly offering multiple virtual submodules.
Definition gsdml.h:84
std::string id
GSDML ID attribute (used to resolve ModuleItemTarget references).
Definition gsdml.h:86
std::vector< GSDMLSubmoduleReference > submoduleReferences
References to globally defined SubmoduleItem definitions.
Definition gsdml.h:103
std::map< std::string, GSDMLSubmodule > submodules
Virtual submodules this module offers, keyed by their GSDML ID.
Definition gsdml.h:96
std::string name
Human-readable module name (from the GSDML's Name/TextId, resolved via ExternalTextList).
Definition gsdml.h:90
std::vector< int > physicalSubslots
Physical subslots supported by this module.
Definition gsdml.h:106
std::uint32_t moduleIdentNumber
Module ident number (hex value from the GSDML, parsed to an integer).
Definition gsdml.h:88
Reference to a submodule that may be used by a module.
Definition gsdml.h:42
std::vector< int > usedSubslots
Subslots where this submodule is currently used/preselected.
Definition gsdml.h:53
std::vector< int > allowedSubslots
Subslots where this submodule is allowed.
Definition gsdml.h:47
std::vector< int > fixedSubslots
Subslots where this submodule is fixed.
Definition gsdml.h:50
std::string submoduleId
Referenced SubmoduleItem ID.
Definition gsdml.h:44
A GSDML virtual submodule definition (VirtualSubmoduleItem).
Definition gsdml.h:58
std::uint32_t submoduleIdentNumber
Submodule ident number (hex value from the GSDML, parsed to an integer).
Definition gsdml.h:62
std::string id
GSDML ID attribute (used to resolve VirtualSubmoduleItemTarget references).
Definition gsdml.h:60
int outputLength
Total output data length in bytes (sum of Output DataItems).
Definition gsdml.h:68
int inputLength
Total input data length in bytes (sum of Input DataItems).
Definition gsdml.h:66
A GSDML system-defined submodule (e.g. built into the DAP: interface, port).
Definition gsdml.h:115
std::uint16_t subslot
Subslot this submodule occupies.
Definition gsdml.h:119
std::uint32_t submoduleIdentNumber
Submodule ident number.
Definition gsdml.h:117
One slot/subslot's expected module configuration and IO data sizes.
Definition rpcTypes.h:91
std::uint16_t inputLength
Expected input data length in bytes (0 if no input data).
Definition rpcTypes.h:99
std::uint16_t slot
Slot number.
Definition rpcTypes.h:93
std::uint16_t outputLength
Expected output data length in bytes (0 if no output data).
Definition rpcTypes.h:102
bool outputIoData
IODataObject for Output CR.
Definition rpcTypes.h:116
bool inputIoData
IODataObject for Input CR.
Definition rpcTypes.h:114
std::uint32_t moduleIdent
Expected module ident number.
Definition rpcTypes.h:105
std::uint32_t submoduleIdent
Expected submodule ident number.
Definition rpcTypes.h:108
std::uint16_t subslot
Subslot number.
Definition rpcTypes.h:96