diff --git a/nodes/nodes/ntx.h b/nodes/nodes/ntx.h new file mode 100644 index 0000000..18a8f92 --- /dev/null +++ b/nodes/nodes/ntx.h @@ -0,0 +1,90 @@ +#ifndef NTX_H +#define NTX_H + +#include +#include +#include + + +/** +* @brief Basistypen für die Kernbibliothek +* unser string typ, ohne den namespace +* +* Wir nehmen hier std::string, der immer UTF-8 kodiert ist. +* std::wstring ist plattformabhängig und führt zu Problemen. (Windows UTF-16 vs. Linux UTF-32) +* std::string_view für als mögliche Alternative zu const std::string& +*/ + +using NtxString = std::string; +using NtxStringView = std::string_view; +using NtxStringList = std::vector; + +/** +* @file ntx.h +* Enthält die grundlegenden Typdefinitionen und Hilfsfunktionen +* für die Kernbibliothek, z.B. NtxClassTypeId, NtxVariant und String-Konvertierungen. +* Alle anderen Header (z.B. ntxinode.h, ntxipayload.h) inkludieren dieses als Basis. +*/ + +namespace ntx +{ + + // Datentyp für die Typid einer Klasse von Knoten (_nicht_ eines einzelen Knotens) + // Option A: Performant (Aktuell) + using NtxClassTypeId = uint64_t; + + // Option B: Flexibel / Lesbar (Zukunft?) + // using NtxClassTypeId = std::string; + + // Option C: Legacy / Embedded + // using NtxClassTypeId = uint32_t; + + // Generischer Zugriff über std::variant (wie eingangs besprochen) + using NtxVariant = std::variant; + + inline bool isEmpty(const NtxVariant& v) { + return std::holds_alternative(v); + } + + template + struct overload : Ts... { using Ts::operator()...; }; + + inline NtxString variantToString(const NtxVariant& v) + { + return std::visit(overload + { + [](std::monostate) { return NtxString{}; }, + [](const NtxString& s) { return s; }, + [](bool b) { return NtxString{b ? "true" : "false"}; }, + [](auto num) { return std::to_string(num); } // Catch-all für int, double + }, v); + } + + // Core (UTF-8) -> Qt (UTF-16) + // Uses SSO (Small String Optimization) from Qt where possible. + inline QString toQt(const NtxString& str) + { + return QString::fromUtf8(str.data(), static_cast(str.size())); + } + + inline QString toQt(NtxStringView str) + { + return QString::fromUtf8(str.data(), static_cast(str.size())); + } + + // Qt (UTF-16) -> Core (UTF-8) + inline NtxString fromQt(const QString& qstr) + { + return qstr.toStdString(); // Qt converts this internally via toUtf8() + } + + inline QString toQString(const NtxVariant& variant) + { + return toQt(variantToString(variant)); + } + +} // namespace ntx + + +#endif // NTX_H + diff --git a/nodes/nodes/ntxcompostionnode.h b/nodes/nodes/ntxcompostionnode.h new file mode 100644 index 0000000..e78ee53 --- /dev/null +++ b/nodes/nodes/ntxcompostionnode.h @@ -0,0 +1,73 @@ +#ifndef NTX_HASANODE_H +#define NTX_HASANODE_H + +#include +#include +#include +#include + +#include + +namespace ntx +{ + + /** + * @brief Implementierung via Komposition (Has-A). + * + * Hält die Payload als Member 'm_payload'. + */ + template + class NtxHasANode : public NtxNodeBase + { + + public: + + // Konstruktor forwarding an m_payload + template + explicit NtxHasANode(Args&&... args) + : NtxNodeBase{}, + m_payload{std::forward(args)...} + { + } + + // ======================================== + // NtxINode Interface: Payload Forwarding + // ======================================== + + size_t getValueCount() const override { return m_payload.getValueCount(); } + void clearValues() override { m_payload.clearValues(); } + + bool hasValue(const NtxString& key) const override { return m_payload.hasValue(key); } + void setValue(const NtxString& key, const NtxVariant& value) override { m_payload.setValue(key, value); } + NtxVariant getValue(const NtxString& key) const override { return m_payload.getValue(key); } + void removeValue(const NtxString& key) override { m_payload.removeValue(key); } + + bool hasValue(size_t index) const override { return m_payload.hasValue(index); } + void setValue(size_t index, const NtxVariant& value) override { m_payload.setValue(index, value); } + NtxVariant getValue(size_t index) const override { return m_payload.getValue(index); } + + // Deducing This Support (Raw Access) + // TPayload muss doGetValues() oder Zugriff auf den Vektor unterstützen + std::vector& doGetValues() override { return m_payload.doGetValues(); } + const std::vector& doGetValues() const override { return m_payload.doGetValues(); } + + void forEachAttribute(NtxPayloadVisitor visitor) const override + { + m_payload.forEachAttribute(visitor); + } + + NtxNodePtr clone() const override + { + auto newNode = std::make_shared>(); + newNode->m_payload = this->m_payload; // Copy Payload + newNode->setClassTypeId(this->getClassTypeId()); + return newNode; + } + + protected: + TPayload m_payload; + }; + +} // namespace ntx + +#endif // NTX_HASANODE_H \ No newline at end of file diff --git a/nodes/nodes/ntxibasicnode.cpp b/nodes/nodes/ntxibasicnode.cpp new file mode 100644 index 0000000..28fca30 --- /dev/null +++ b/nodes/nodes/ntxibasicnode.cpp @@ -0,0 +1,115 @@ +#include +#include + +namespace ntx +{ + + // ======================================== + // NtxINode Interface: CType Aspekt + // ======================================== + + const NtxClassTypeId NtxIBasicNode::getClassTypeId() const + { + return m_ctypeId; + } + + const NtxString& NtxIBasicNode::getClassTypeLabel() const + { + return NtxNodeFactory::lookupCTypeLabel(getClassTypeId()); + } + + // ======================================== + // NtxINode Interface: Tree Structure + // ======================================== + + void NtxIBasicNode::setParent(NtxNodePtr parent) + { + m_parent = parent; + } + + size_t NtxIBasicNode::getChildCount() const + { + return m_children.size(); + } + + bool NtxIBasicNode::hasChildren() const + { + return !m_children.empty(); + } + + void NtxIBasicNode::addChild(NtxNodePtr child) + { + if (child) + { + m_children.push_back(child); + child->setParent(this->shared_from_this()); + } + } + + void NtxIBasicNode::insertChild(size_t index, NtxNodePtr child) + { + if (!child) return; + + if (index >= m_children.size()) + m_children.push_back(child); + else + m_children.insert(m_children.begin() + index, child); + + child->setParent(this->shared_from_this()); + } + + bool NtxIBasicNode::removeChild(size_t index) + { + if (index >= m_children.size()) return false; + + if (m_children[index]) + m_children[index]->setParent(nullptr); + + m_children.erase(m_children.begin() + index); + return true; + } + + size_t NtxIBasicNode::getChildIndex(const NtxNodePtr& child) const + { + for (size_t i = 0; i < m_children.size(); ++i) + { + if (m_children[i] == child) return i; + } + return static_cast(-1); + } + + void NtxIBasicNode::clearChildren() + { + for (auto& child : m_children) + { + if (child) child->setParent(nullptr); + } + m_children.clear(); + } + + // ======================================== + // Protected Hooks + // ======================================== + + void NtxIBasicNode::setClassTypeLabel(const NtxString& idString) + { + setClassTypeId(NtxNodeFactory::generateCTypeId(idString)); + } + + void NtxIBasicNode::setClassTypeId(NtxClassTypeId classTypeId) + { + m_ctypeId = classTypeId; + } + + NtxNodePtr NtxIBasicNode::doGetParent() const noexcept + { + return m_parent.lock(); + } + + NtxNodePtr NtxIBasicNode::doGetChild(size_t index) const noexcept + { + if (index >= m_children.size()) return nullptr; + return m_children[index]; + } + +} // namespace ntx \ No newline at end of file diff --git a/nodes/nodes/ntxibasicnode.h b/nodes/nodes/ntxibasicnode.h new file mode 100644 index 0000000..1d50312 --- /dev/null +++ b/nodes/nodes/ntxibasicnode.h @@ -0,0 +1,66 @@ +#ifndef NTX_NODEBASE_H +#define NTX_NODEBASE_H + +#include +#include +#include + +namespace ntx +{ + + /** + * @brief Abstrakte Basisklasse, die Baumstruktur und Typ-Information verwaltet. + * + * Implementiert alle Aspekte von NtxINode, die unabhängig von der Payload sind. + */ + class NtxIBasicNode : public NtxINode + { + + public: + + NtxIBasicNode() = default; + ~NtxIBasicNode() override = default; + + // ======================================== + // NtxINode Interface: CType Aspekt + // ======================================== + + const NtxClassTypeId getClassTypeId() const override; + const NtxString& getClassTypeLabel() const override; + + // ======================================== + // NtxINode Interface: Tree Structure + // ======================================== + + void setParent(NtxNodePtr parent) override; + + size_t getChildCount() const override; + bool hasChildren() const override; + + void addChild(NtxNodePtr child) override; + void insertChild(size_t index, NtxNodePtr child) override; + bool removeChild(size_t index) override; + size_t getChildIndex(const NtxNodePtr& child) const override; + void clearChildren() override; + + protected: + + // --- Implementierung virtueller Hooks --- + + void setClassTypeLabel(const NtxString& idString) override; + void setClassTypeId(NtxClassTypeId classTypeId) override; + + NtxNodePtr doGetParent() const noexcept override; + NtxNodePtr doGetChild(size_t index) const noexcept override; + + protected: + + NtxClassTypeId m_ctypeId{}; + NtxNodeWeakPtr m_parent; + NtxNodeList m_children; + + }; + +} // namespace ntx + +#endif // NTX_NODEBASE_H \ No newline at end of file diff --git a/nodes/nodes/ntxiclasstype.h b/nodes/nodes/ntxiclasstype.h new file mode 100644 index 0000000..efb7828 --- /dev/null +++ b/nodes/nodes/ntxiclasstype.h @@ -0,0 +1,37 @@ +#ifndef NTX_ICLASSTYPE_H +#define NTX_ICLASSTYPE_H + +#include + +namespace ntx +{ + + /** + * @brief Minimales Interface für den 'ClassType' Aspekt: Der CType entspricht + * dem Element-namen eines Knotens im XML. Die ClassTypeId ist eine performante + * Repräsentation (z.B. Hash) dieses Namens. Kann unterschiedliche Implementierungen haben, + * z.B.: einfaches Hochzählen, Hash des Strings, den String selbst oder eine Meta-Klasse, + * vgl. QObject. + */ + + class NtxIClassType + { + + public: + + virtual ~NtxIClassType() = default; + + virtual const NtxClassTypeId getClassTypeId() const = 0; + virtual const NtxString& getClassTypeLabel() const = 0; + + protected: + + // ✅ Protected - nur abgeleitete Klassen und Friends + virtual void setClassTypeLabel(const NtxString& idString) = 0; + virtual void setClassTypeId(NtxClassTypeId classTypeId) = 0; + + }; + +} // namespace ntx + +#endif // NTX_ICLASSTYPE_H \ No newline at end of file diff --git a/nodes/nodes/ntxiclasstyperepo.cpp b/nodes/nodes/ntxiclasstyperepo.cpp new file mode 100644 index 0000000..68f8691 --- /dev/null +++ b/nodes/nodes/ntxiclasstyperepo.cpp @@ -0,0 +1,143 @@ + + +#include + +namespace ntx +{ + // ========================================================= + // NtxCTypeHashRepo + // ========================================================= + + NtxClassTypeId NtxCTypeHashRepo::generateId(const NtxString& label) + { + std::hash hasher; + // Expliziter Cast falls NtxClassTypeId kleiner als size_t ist + NtxClassTypeId id = static_cast(hasher(label)); + + // Optimistischer Check (Read Lock) + { + std::shared_lock lock(m_mutex); + if (m_idToLabel.find(id) != m_idToLabel.end()) + return id; + } + + // Registrieren (Write Lock) + { + std::unique_lock lock(m_mutex); + m_idToLabel[id] = label; + } + return id; + } + + NtxClassTypeId NtxCTypeHashRepo::lookupId(const NtxString& label) const + { + std::hash hasher; + NtxClassTypeId id = static_cast(hasher(label)); + + if (isRegistered(id)) + return id; + + return {}; // Default constructed ID (0) + } + + const NtxString& NtxCTypeHashRepo::lookupLabel(NtxClassTypeId id) const + { + std::shared_lock lock(m_mutex); + auto it = m_idToLabel.find(id); + if (it != m_idToLabel.end()) + return it->second; + + static const NtxString empty; + return empty; + } + + bool NtxCTypeHashRepo::isRegistered(NtxClassTypeId id) const + { + std::shared_lock lock(m_mutex); + return m_idToLabel.find(id) != m_idToLabel.end(); + } + + bool NtxCTypeHashRepo::isRegistered(const NtxString& label) const + { + std::hash hasher; + return isRegistered(static_cast(hasher(label))); + } + + size_t NtxCTypeHashRepo::getRegisteredCount() const + { + std::shared_lock lock(m_mutex); + return m_idToLabel.size(); + } + + + // ========================================================= + // NtxCTypeCounterRepo + // ========================================================= + + NtxCTypeCounterRepo::NtxCTypeCounterRepo() + : m_nextId(1) // Start bei 1, 0 ist invalid + { + } + + NtxClassTypeId NtxCTypeCounterRepo::generateId(const NtxString& label) + { + { + std::shared_lock lock(m_mutex); + auto it = m_labelToId.find(label); + if (it != m_labelToId.end()) + return it->second; + } + + { + std::unique_lock lock(m_mutex); + // Double check + if (auto it = m_labelToId.find(label); it != m_labelToId.end()) + return it->second; + + NtxClassTypeId newId = m_nextId++; + m_idToLabel[newId] = label; + m_labelToId[label] = newId; + + return newId; + } + } + + NtxClassTypeId NtxCTypeCounterRepo::lookupId(const NtxString& label) const + { + std::shared_lock lock(m_mutex); + auto it = m_labelToId.find(label); + if (it != m_labelToId.end()) + return it->second; + return {}; + } + + const NtxString& NtxCTypeCounterRepo::lookupLabel(NtxClassTypeId id) const + { + std::shared_lock lock(m_mutex); + auto it = m_idToLabel.find(id); + if (it != m_idToLabel.end()) + return it->second; + + static const NtxString empty; + return empty; + } + + bool NtxCTypeCounterRepo::isRegistered(NtxClassTypeId id) const + { + std::shared_lock lock(m_mutex); + return m_idToLabel.find(id) != m_idToLabel.end(); + } + + bool NtxCTypeCounterRepo::isRegistered(const NtxString& label) const + { + std::shared_lock lock(m_mutex); + return m_labelToId.find(label) != m_labelToId.end(); + } + + size_t NtxCTypeCounterRepo::getRegisteredCount() const + { + std::shared_lock lock(m_mutex); + return m_idToLabel.size(); + } + +} // namespace ntx \ No newline at end of file diff --git a/nodes/nodes/ntxiclasstyperepo.h b/nodes/nodes/ntxiclasstyperepo.h new file mode 100644 index 0000000..bd0e28f --- /dev/null +++ b/nodes/nodes/ntxiclasstyperepo.h @@ -0,0 +1,126 @@ +#ifndef NTX_CTYPEREPOSITORY_H +#define NTX_CTYPEREPOSITORY_H + +#include +#include +#include +#include +#include + +namespace ntx +{ + /** + * @brief Repository Interface für CType ID Management. + * Entkoppelt die ID-Generierungsstrategie von der Knoten-Implementierung. + */ + class NtxIClassTypeRepo + { + + public: + + virtual ~NtxIClassTypeRepo() = default; + + // Generiert oder holt eine ID für das Label + virtual NtxClassTypeId generateId(const NtxString& label) = 0; + + // Lookups (Thread-safe read) + virtual NtxClassTypeId lookupId(const NtxString& label) const = 0; + virtual const NtxString& lookupLabel(NtxClassTypeId id) const = 0; + + // Checks + virtual bool isRegistered(NtxClassTypeId id) const = 0; + virtual bool isRegistered(const NtxString& label) const = 0; + + // Diagnostics + virtual size_t getRegisteredCount() const = 0; + + }; + + /** + * @brief Hash-basierte Implementierung (std::hash). + */ + class NtxCTypeHashRepo : public NtxIClassTypeRepo + { + + public: + + NtxClassTypeId generateId(const NtxString& label) override; + NtxClassTypeId lookupId(const NtxString& label) const override; + const NtxString& lookupLabel(NtxClassTypeId id) const override; + + bool isRegistered(NtxClassTypeId id) const override; + bool isRegistered(const NtxString& label) const override; + size_t getRegisteredCount() const override; + + private: + + std::unordered_map m_idToLabel; + mutable std::shared_mutex m_mutex; + }; + + /** + * @brief Zähler-basierte Implementierung (1, 2, 3...). + */ + class NtxCTypeCounterRepo : public NtxIClassTypeRepo + { + + public: + + NtxCTypeCounterRepo(); + + NtxClassTypeId generateId(const NtxString& label) override; + NtxClassTypeId lookupId(const NtxString& label) const override; + const NtxString& lookupLabel(NtxClassTypeId id) const override; + + bool isRegistered(NtxClassTypeId id) const override; + bool isRegistered(const NtxString& label) const override; + size_t getRegisteredCount() const override; + + private: + + std::unordered_map m_idToLabel; + std::unordered_map m_labelToId; + NtxClassTypeId m_nextId; + mutable std::shared_mutex m_mutex; + + + /* + #include + #include + #include + + constexpr uint64_t fnv1a_64(std::string_view text) { + uint64_t hash = 14695981039346656037ull; // 64-bit offset + for (char c : text) { + hash ^= static_cast(c); + hash *= 1099511628211ull; // 64-bit prime + } + return hash; + } + + // Ein Funktor (Funktionsobjekt), das die STL-Vorgaben für Hash-Funktionen erfüllt + struct FastFnv1aHasher { + std::size_t operator()(std::string_view sv) const { + // (Nutzt hier die fnv1a_64 Funktion von oben) + return fnv1a_64(sv); + } + }; + + // Deine Map: + // Key: string_view (Zero-Copy) + // Value: MyData + // Hasher: Dein eigener, pfeilschneller FNV-1a (statt dem langsamen std::hash) + std::unordered_map xmlElements; + + OBACHT! Lifetime! Woher kommt die StringView ? + + -> doch: string internisieren ! -> Aber was ist mit copy_over_net ? + claude.ai: String Interning ist die richtige Wahl + + */ + + }; + +} // namespace ntx + +#endif // NTX_CTYPEREPOSITORY_H \ No newline at end of file diff --git a/nodes/nodes/ntxinode.cpp b/nodes/nodes/ntxinode.cpp new file mode 100644 index 0000000..1242612 --- /dev/null +++ b/nodes/nodes/ntxinode.cpp @@ -0,0 +1,33 @@ + + +#include +#include + +namespace ntx +{ + + /* + NtxNodeIterator NtxINode::begin() + { + return NtxNodeIterator(this); + } + + NtxNodeIterator NtxINode::end() + { + return NtxNodeIterator(); + } + + NtxCNodeIterator NtxINode::begin() const + { + return NtxCNodeIterator(this); + } + + NtxCNodeIterator NtxINode::end() const + { + return NtxCNodeIterator(); + } + */ + + +} // namespace ntx + diff --git a/nodes/nodes/ntxinode.h b/nodes/nodes/ntxinode.h new file mode 100644 index 0000000..06f92a3 --- /dev/null +++ b/nodes/nodes/ntxinode.h @@ -0,0 +1,176 @@ +#ifndef NTX_INODE_H +#define NTX_INODE_H + +#include +#include +#include +#include // for std::ranges::subrange +#include // for std::conditional_t, std::is_const_v + +#include +#include +#include +#include + +/** +* @file ntxinode.h +* NtxINode ist die zentrale Schnittstelle, die NtxIClassType und NtxIPayload vereint +* und zusätzlich die Baumstruktur definiert. +*/ + +namespace ntx +{ + + class NtxINode; + + template + struct NtxNodeDepthFirstIterator; + + using NtxNodePtr = std::shared_ptr; + using NtxNodeCPtr = std::shared_ptr; + using NtxNodeWeakPtr = std::weak_ptr; + using NtxNodeList = std::vector; + + using NtxNodeIterator = NtxNodeDepthFirstIterator; + using NtxCNodeIterator = NtxNodeDepthFirstIterator; + + /** + * @brief Zentrales Interface für einen Knoten im Baum. Basiert auf NtxIClassType und NtxIPayload. + * Gibt die Baumstruktur vor (Eltern, Kinder, Geschwister) und bietet Iteratoren für die Traversierung. + * Die konkrete Implementierung erfolgt in NtxNode oder abgeleiteten Klassen. + * Nutzlast- und ClassType-Aspekte können durch Mixins realisiert werden. + */ + + class NtxINode : + + public NtxIClassType, + public NtxIPayload, + public std::enable_shared_from_this + + { + friend class NtxNodeFactory; + friend class NtxLinkNode; + + public: + + virtual ~NtxINode() = default; + + // --- Tree Structure --- + virtual void setParent(NtxNodePtr parent) = 0; + + // Vereinfacht durch Deducing this und if constexpr + template + auto getParent(this Self&& self) + { + auto ptr = self.doGetParent(); + if constexpr (std::is_const_v>) + return std::const_pointer_cast(ptr); + else + return ptr; + } + + template + auto getChild(this Self&& self, size_t index) + { + auto ptr = self.doGetChild(index); + if constexpr (std::is_const_v>) + return std::const_pointer_cast(ptr); + else + return ptr; + } + + template + auto getSibling(this Self&& self) + { + auto parent = self.getParent(); + using ResultType = decltype(parent); // NtxNodePtr oder NtxNodeCPtr + + if (!parent) return ResultType{}; + + const size_t childCount = parent->getChildCount(); + + for (size_t i = 0; i < childCount; ++i) + { + auto child = parent->getChild(i); + // Hinweis: child.get() ist raw-pointer, &self ist Adresse des aktuellen Objekts + if (child.get() == &self) + { + if (i + 1 < childCount) + return parent->getChild(i + 1); + else + return ResultType{}; + } + } + return ResultType{}; + } + + std::optional ownPos() const + { + auto parent = doGetParent(); // Nutze internal (non-deducing) getter um const-cast zu vermeiden + if (!parent) return std::nullopt; + + const size_t childCount = parent->getChildCount(); + + for (size_t i = 0; i < childCount; ++i) + { + auto child = parent->getChild(i); // NtxNodePtr comparison + if (child.get() == this) + return i; + } + + return std::nullopt; + } + + virtual size_t getChildCount() const = 0; + virtual bool hasChildren() const = 0; + + virtual void addChild(NtxNodePtr child) = 0; + virtual void insertChild(size_t index, NtxNodePtr child) = 0; + virtual bool removeChild(size_t index) = 0; + virtual size_t getChildIndex(const NtxNodePtr& child) const = 0; + virtual void clearChildren() = 0; + + virtual NtxNodePtr clone() const = 0; + + + // ======================================================================= + // Unified Iterators via Deducing This + // ======================================================================= + + // Ersetzt begin/end und cbegin/cend Logik + template + auto begin(this Self&& self) + { + using NodeType = std::remove_reference_t; // NtxINode oder const NtxINode + // Konstruktor erwartet Pointer, &self ist sicher + return NtxNodeDepthFirstIterator(&self); + } + + template + auto end(this Self&& self) + { + using NodeType = std::remove_reference_t; + return NtxNodeDepthFirstIterator(); + } + + // ======================================================================= + // Ranges Support (C++23 Style) + // ======================================================================= + + template + auto descendants(this Self&& self) + { + return std::ranges::subrange(self.begin(), self.end()); + } + + protected: + + virtual NtxNodePtr doGetParent() const = 0; + virtual NtxNodePtr doGetChild(size_t index) const = 0; + + }; + +} // namespace ntx + +#endif // NTX_INODE_H + diff --git a/nodes/nodes/ntxipayload.h b/nodes/nodes/ntxipayload.h new file mode 100644 index 0000000..e1e938e --- /dev/null +++ b/nodes/nodes/ntxipayload.h @@ -0,0 +1,98 @@ +#ifndef NTX_IPAYLOAD_H +#define NTX_IPAYLOAD_H + +#include +#include +#include +#include // for std::get_if +#include // for std::function +#include // for std::remove_reference_t + +#include + +namespace ntx +{ + + class NtxIPayload; + + using NtxPayloadVisitor = std::function const; + using NtxPayloadPtr = std::shared_ptr; + + // --- Payload Iterator-Typen (analog zu NtxMaptor) --- + using NtxPayloadIterator = std::vector::iterator; + using NtxPayloadCIterator = std::vector::const_iterator; + + + /** + * @brief Minimales Interface für 'Nutzlast' Aspekt. Kann beliebige Key-Value Paare speichern, + * wobei die Werte über NtxVariant typisiert sind. Implementierungen können intern + * z.B. std::unordered_map verwenden, einen 'Maptor' oder auf eine Meta-Klasse verweisen + * Das Interface bietet auch typsichere Zugriffsmethoden über Templates, die auf std::variant basieren. + */ + + class NtxIPayload + { + + public: + + virtual ~NtxIPayload() = default; + + virtual size_t getPropertyCount() const = 0; + virtual void clearProperties() = 0; + + // --- NtxIPayload Interface: Key-basiert --- + virtual bool hasProperty(const NtxString& key) const = 0; + virtual void setProperty(const NtxString& key, const NtxVariant& value) = 0; + virtual NtxVariant getProperty(const NtxString& key) const = 0; + virtual void removeProperty(const NtxString& key) = 0; + + // --- NtxIPayload Interface: Index-basiert --- + virtual bool hasProperty(size_t index) const = 0; + virtual void setProperty(size_t index, const NtxVariant& value) = 0; + virtual NtxVariant getProperty(size_t index) const = 0; + + //virtual NtxIPayload clone() const = 0; + template + TPayload clonePayload() const + { + return TPayload(*this); + } + + // --- Payload Wrapper (Convenience) --- + template + void set(const NtxString& key, T&& value) + { + setProperty(key, NtxVariant(std::forward(value))); + } + + template + std::optional get(const NtxString& key) const + { + // 1. Hole rohen Variant + NtxVariant raw = getProperty(key); + + // 2. Prüfe ob der Typ T im Variant aktiv ist + // std::get_if liefert nullptr, wenn der Typ nicht passt. + if (const T* valPtr = std::get_if(&raw)) + return *valPtr; + // Typ passt nicht oder Key existiert nicht (monostate) + return std::nullopt; + } + + // @brief Komfort-Überladung: Liefert 'defaultValue', wenn Key fehlt oder Typ falsch ist. + template + T get(const NtxString& key, const T& defaultValue) const + { + auto res = get(key); + if (res.has_value()) + return res.value(); + return defaultValue; + } + + virtual void forEachProperty(NtxPayloadVisitor visitor) const = 0; + + }; + +} // namespace ntx + +#endif // NTX_IPAYLOAD_H \ No newline at end of file diff --git a/nodes/nodes/ntxlinknode.cpp b/nodes/nodes/ntxlinknode.cpp new file mode 100644 index 0000000..e0045fe --- /dev/null +++ b/nodes/nodes/ntxlinknode.cpp @@ -0,0 +1,212 @@ +#include +#include + +#include + +namespace ntx +{ + + class NtxProxyNodeExpiredException : public std::runtime_error + { + + public: + + using std::runtime_error::runtime_error; + }; + + namespace + { + NtxNodePtr lockOriginalNodeOrThrow(const NtxNodeWeakPtr& original) + { + auto orig = original.lock(); + if (!orig) + throw NtxProxyNodeExpiredException("NtxLinkNode: Original node expired"); + return orig; + } + + NtxNodeCPtr lockOriginalNodeOrThrowConst(const NtxNodeWeakPtr& original) + { + auto orig = original.lock(); + if (!orig) + throw NtxProxyNodeExpiredException("NtxLinkNode: Original node expired"); + return orig; + } + } + + // Private constructor + NtxLinkNode::NtxLinkNode(NtxNodePtr originalNode) + : m_originalNode{originalNode} + { + if (!originalNode) + { + throw std::invalid_argument("NtxLinkNode: Original node cannot be nullptr"); + } + } + + // Factory method + NtxNodePtr NtxLinkNode::makeLink(NtxNodePtr originalNode) + { + return std::shared_ptr(new NtxLinkNode(originalNode)); + } + + void NtxLinkNode::setParent(NtxNodePtr parent) + { + lockOriginalNodeOrThrow(m_originalNode)->setParent(parent); + } + + // ========================================================================= + // Type Access + // ========================================================================= + + const NtxClassTypeId NtxLinkNode::getClassTypeId() const + { + return lockOriginalNodeOrThrow(m_originalNode)->getClassTypeId(); + } + + const NtxString& NtxLinkNode::getClassTypeLabel() const + { + return lockOriginalNodeOrThrow(m_originalNode)->getClassTypeLabel(); + } + + void NtxLinkNode::setClassTypeLabel(const NtxString& idString) + { + lockOriginalNodeOrThrow(m_originalNode)->setClassTypeLabel(idString); + } + + void NtxLinkNode::setClassTypeId(NtxClassTypeId classTypeId) + { + lockOriginalNodeOrThrow(m_originalNode)->setClassTypeId(classTypeId); + } + + // ========================================================================= + // Original Node Access + // ========================================================================= + + NtxNodePtr NtxLinkNode::getOriginalNode() + { + return lockOriginalNodeOrThrow(m_originalNode); + } + + NtxNodeCPtr NtxLinkNode::getOriginalNode() const + { + return lockOriginalNodeOrThrowConst(m_originalNode); + } + + bool NtxLinkNode::isValid() const + { + return !m_originalNode.expired(); + } + + // ========================================================================= + // Payload: Key-basiert + // ========================================================================= + + bool NtxLinkNode::hasProperty(const NtxString& key) const + { + return lockOriginalNodeOrThrow(m_originalNode)->hasProperty(key); + } + + void NtxLinkNode::setProperty(const NtxString& key, const NtxVariant& value) + { + lockOriginalNodeOrThrow(m_originalNode)->setProperty(key, value); + } + + NtxVariant NtxLinkNode::getProperty(const NtxString& key) const + { + return lockOriginalNodeOrThrow(m_originalNode)->getProperty(key); + } + + void NtxLinkNode::removeProperty(const NtxString& key) + { + lockOriginalNodeOrThrow(m_originalNode)->removeProperty(key); + } + + void NtxLinkNode::clearProperties() + { + lockOriginalNodeOrThrow(m_originalNode)->clearProperties(); + } + + void NtxLinkNode::forEachProperty(NtxPayloadVisitor visitor) const + { + lockOriginalNodeOrThrow(m_originalNode)->forEachProperty(visitor); + } + + // ========================================================================= + // Payload: Index-basiert + // ========================================================================= + + bool NtxLinkNode::hasProperty(size_t index) const + { + return lockOriginalNodeOrThrow(m_originalNode)->hasProperty(index); + } + + void NtxLinkNode::setProperty(size_t index, const NtxVariant& value) + { + lockOriginalNodeOrThrow(m_originalNode)->setProperty(index, value); + } + + NtxVariant NtxLinkNode::getProperty(size_t index) const + { + return lockOriginalNodeOrThrow(m_originalNode)->getProperty(index); + } + + // ========================================================================= + // Children + // ========================================================================= + + size_t NtxLinkNode::getChildCount() const + { + return lockOriginalNodeOrThrow(m_originalNode)->getChildCount(); + } + + bool NtxLinkNode::hasChildren() const + { + return lockOriginalNodeOrThrow(m_originalNode)->hasChildren(); + } + + NtxNodePtr NtxLinkNode::doGetParent() const + { + return lockOriginalNodeOrThrow(m_originalNode)->doGetParent(); + } + + NtxNodePtr NtxLinkNode::doGetChild(size_t index) const + { + return lockOriginalNodeOrThrow(m_originalNode)->getChild(index); + } + + void NtxLinkNode::addChild(NtxNodePtr child) + { + lockOriginalNodeOrThrow(m_originalNode)->addChild(child); + } + + bool NtxLinkNode::removeChild(size_t index) + { + return lockOriginalNodeOrThrow(m_originalNode)->removeChild(index); + } + + void NtxLinkNode::clearChildren() + { + lockOriginalNodeOrThrow(m_originalNode)->clearChildren(); + } + + NtxNodePtr NtxLinkNode::clone() const + { + return lockOriginalNodeOrThrow(m_originalNode)->clone(); + } + + void NtxLinkNode::insertChild(size_t index, NtxNodePtr child) + { + lockOriginalNodeOrThrow(m_originalNode)->insertChild(index, child); + } + + size_t NtxLinkNode::getChildIndex(const NtxNodePtr& child) const + { + return lockOriginalNodeOrThrow(m_originalNode)->getChildIndex(child); + } + + size_t NtxLinkNode::getPropertyCount() const + { + return lockOriginalNodeOrThrow(m_originalNode)->getPropertyCount(); + } + +} // namespace ntx \ No newline at end of file diff --git a/nodes/nodes/ntxlinknode.h b/nodes/nodes/ntxlinknode.h new file mode 100644 index 0000000..4c0cbcc --- /dev/null +++ b/nodes/nodes/ntxlinknode.h @@ -0,0 +1,72 @@ +#ifndef NTX_LINKNODE_H +#define NTX_LINKNODE_H + +#include + +namespace ntx +{ + + class NtxLinkNode : public NtxINode + { + + public: + + // Factory method to create a link to an original node + static NtxNodePtr makeLink(NtxNodePtr originalNode); + + void setParent(NtxNodePtr parent) override; + + const NtxClassTypeId getClassTypeId() const override; + const NtxString& getClassTypeLabel() const override; + void setClassTypeLabel(const NtxString& idString) override; + void setClassTypeId(const NtxClassTypeId classTypeId) override; + + // Kinder + size_t getChildCount() const override; + bool hasChildren() const override; + + void addChild(NtxNodePtr child) override; + void insertChild(size_t index, NtxNodePtr child) override; + bool removeChild(size_t index) override; + size_t getChildIndex(const NtxNodePtr& child) const override; + size_t getPropertyCount() const override; + void clearChildren() override; + + NtxNodePtr clone() const override; + + // Payload: Key-basiert + bool hasProperty(const NtxString& key) const override; + void setProperty(const NtxString& key, const NtxVariant& value) override; + NtxVariant getProperty(const NtxString& key) const override; + void removeProperty(const NtxString& key) override; + void clearProperties() override; + void forEachProperty(NtxPayloadVisitor visitor) const override; + + // Payload: Index-basiert + bool hasProperty(size_t index) const override; + void setProperty(size_t index, const NtxVariant& value) override; + NtxVariant getProperty(size_t index) const override; + + // Returns the original node this link points to + NtxNodePtr getOriginalNode(); + NtxNodeCPtr getOriginalNode() const; + + // Checks if the link is still valid (original node exists) + bool isValid() const; + + private: + + NtxNodePtr doGetParent() const override; + NtxNodePtr doGetChild(size_t index) const override; + + // Private constructor - use makeLink() factory method + explicit NtxLinkNode(NtxNodePtr originalNode); + + // Weak pointer to avoid circular references + NtxNodeWeakPtr m_originalNode; + + }; + +} // namespace ntx + +#endif // NTX_LINKNODE_H \ No newline at end of file diff --git a/nodes/nodes/ntxnode.cpp b/nodes/nodes/ntxnode.cpp new file mode 100644 index 0000000..72fa810 --- /dev/null +++ b/nodes/nodes/ntxnode.cpp @@ -0,0 +1,18 @@ +#include +#include + +namespace ntx +{ + // Für Factory mit CTypeId + NtxNode::NtxNode(NtxClassTypeId id) + : NtxNode() + { + setClassTypeId(id); + } + + +} + + + + diff --git a/nodes/nodes/ntxnode.h b/nodes/nodes/ntxnode.h new file mode 100644 index 0000000..8a5db45 --- /dev/null +++ b/nodes/nodes/ntxnode.h @@ -0,0 +1,45 @@ +#ifndef NTX_NODE_H +#define NTX_NODE_H + +#include +#include +#include +#include + +namespace ntx +{ + class NtxNodeFactory; + + using NtxVariantMaptor = NtxMaptor; + using NtxMixInNode = NtxTMixInNode; + using NtxAggregateNode = NtxTAggregateNode; + + //class NtxNode : public NtxTCloneable + class NtxNode : public NtxTCloneable + { + friend class NtxNodeFactory; + friend class NtxLinkNode; + + public: + + NtxNode() = default; + ~NtxNode() override = default; + + // Copy-Ctor wird von NtxTCloneable::clone() benötigt + NtxNode(const NtxNode&) = default; + NtxNode& operator=(const NtxNode&) = delete; + + NtxNode(NtxNode&&) noexcept = default; + NtxNode& operator=(NtxNode&&) noexcept = default; + + // clone() wird automatisch durch NtxTCloneable generiert + + private: + + explicit NtxNode(NtxClassTypeId id); + + }; + +} // namespace ntx + +#endif // NTX_NODE_H \ No newline at end of file diff --git a/nodes/nodes/ntxnodefactory.cpp b/nodes/nodes/ntxnodefactory.cpp new file mode 100644 index 0000000..a3e317f --- /dev/null +++ b/nodes/nodes/ntxnodefactory.cpp @@ -0,0 +1,112 @@ +#include + +#include "ntxnodefactory.h" +#include "ntxnode.h" +#include // implementation of default repository + + +namespace ntx +{ + + NtxNodeFactory::NtxNodeFactory() + : m_ctypeRepository(std::make_unique()) // Default: Hash-basiert + { + } + + NtxNodeFactory& NtxNodeFactory::getInstance() + { + static NtxNodeFactory instance; + return instance; + } + + const NtxString& NtxNodeFactory::lookupCTypeLabel(NtxClassTypeId id) + { + return getInstance().getCTypeRepository().lookupLabel(id); + } + + NtxClassTypeId NtxNodeFactory::generateCTypeId(const NtxString& cTypeLabel) + { + return getInstance().getCTypeRepository().generateId(cTypeLabel); + } + + + NtxNodePtr NtxNodeFactory::makeNode(const NtxString& label) + { + return getInstance().makeNodeByLabel(label); + } + + NtxNodePtr NtxNodeFactory::makeNode(NtxClassTypeId id) + { + return getInstance().makeNodeById(id); + } + + NtxNodePtr NtxNodeFactory::makeSharedNode(const NtxString& label, NtxNodePtr sharedSource) + { + return getInstance().makeSharedNodeByLabel(label, sharedSource); + } + + NtxNodePtr NtxNodeFactory::makeSharedNode(NtxClassTypeId id, NtxNodePtr sharedSource) + { + return getInstance().makeSharedNodeById(id, sharedSource); + } + + /* + Geile Recursion! + NtxNodePtr NtxNodeFactory::cloneNode(NtxNodeCPtr originalNode) + { + return originalNode->clone(); + } + */ + + + NtxNodePtr NtxNodeFactory::makeNodeByLabel(const NtxString& ctypelabel) + { + // ID generieren und zuweisen + NtxClassTypeId id = m_ctypeRepository->generateId(ctypelabel); + return makeNodeById(id); + } + + NtxNodePtr NtxNodeFactory::makeNodeById(NtxClassTypeId id) + { + // Node erstellen über friend-Zugriff + auto node = std::shared_ptr(new NtxNode(id)); + + // Label aus Repository holen (wird in NtxIBasicNode::getClassTypeLabel() automatisch gemacht) + // Kein expliziter setClassTypeLabel-Aufruf nötig + + return node; + } + + NtxNodePtr NtxNodeFactory::makeSharedNodeByLabel(const NtxString& label, NtxNodePtr sharedSource) + { + // ID generieren und zuweisen + NtxClassTypeId id = m_ctypeRepository->generateId(label); + return makeSharedNodeById(id, sharedSource); + } + + NtxNodePtr NtxNodeFactory::makeSharedNodeById(NtxClassTypeId id, NtxNodePtr sharedSource) + { + return nullptr; // std::shared_ptr(new NtxSharedNode(id, sharedSource)); + } + + + // --------------------------------------------------------------------------------- + + + NtxIClassTypeRepo& NtxNodeFactory::getCTypeRepository() + { + return *m_ctypeRepository; + } + + const NtxIClassTypeRepo& NtxNodeFactory::getCTypeRepository() const + { + return *m_ctypeRepository; + } + + void NtxNodeFactory::setCTypeRepository(std::unique_ptr repo) + { + if (repo) + m_ctypeRepository = std::move(repo); + } + +} // namespace ntx diff --git a/nodes/nodes/ntxnodefactory.h b/nodes/nodes/ntxnodefactory.h new file mode 100644 index 0000000..c23476e --- /dev/null +++ b/nodes/nodes/ntxnodefactory.h @@ -0,0 +1,59 @@ +#ifndef NTX_NODEFACTORY_H +#define NTX_NODEFACTORY_H + +#include + +#include +#include // Include for repo interface + + +namespace ntx +{ + /** + * @brief Factory für die Erstellung von Nodes. Das Management von den ClassTypeIds und ihren + * ClassTypeLabels wird hier versteckt. @see NtxIClassTypeRepo + */ + class NtxNodeFactory + { + public: + + // Singleton-Zugriff auf die Factory-Instanz. + static NtxNodeFactory& getInstance(); + // Statische Hilfsmethoden für CType-Management und Node-Erstellung. + static const NtxString& lookupCTypeLabel(NtxClassTypeId id); + static NtxClassTypeId generateCTypeId(const NtxString& cTypeLabel); + + static NtxNodePtr makeNode(const NtxString& label); + static NtxNodePtr makeNode(NtxClassTypeId id); + static NtxNodePtr makeSharedNode(const NtxString& label, NtxNodePtr sharedSource); + static NtxNodePtr makeSharedNode(NtxClassTypeId id, NtxNodePtr sharedSource); + //static NtxNodePtr makeLinkedNode(const NtxString& label, NtxNodePtr sharedSource); + //static NtxNodePtr makeLinkedNode(NtxClassTypeId id, NtxNodePtr sharedSource); + + //static NtxNodePtr cloneNode(NtxNodeCPtr originalNode); + + NtxNodePtr makeNodeByLabel(const NtxString& label); + NtxNodePtr makeNodeById(NtxClassTypeId id); + NtxNodePtr makeSharedNodeByLabel(const NtxString& label, NtxNodePtr sharedSource); + NtxNodePtr makeSharedNodeById(NtxClassTypeId id, NtxNodePtr sharedSource); + + NtxIClassTypeRepo& getCTypeRepository(); + const NtxIClassTypeRepo& getCTypeRepository() const; + void setCTypeRepository(std::unique_ptr repo); + + private: + + NtxNodeFactory(); + ~NtxNodeFactory() = default; + + // Nicht kopierbar + NtxNodeFactory(const NtxNodeFactory&) = delete; + NtxNodeFactory& operator=(const NtxNodeFactory&) = delete; + + // Das zentrale Repository für alle Nodes + std::unique_ptr m_ctypeRepository; + }; + +} // namespace ntx + +#endif // NTX_NODEFACTORY_H diff --git a/nodes/nodes/ntxnodeiterators.h b/nodes/nodes/ntxnodeiterators.h new file mode 100644 index 0000000..25ca6a4 --- /dev/null +++ b/nodes/nodes/ntxnodeiterators.h @@ -0,0 +1,127 @@ +#ifndef NTXNODE_ITERATORS_H +#define NTXNODE_ITERATORS_H + +#include +#include +#include // Wichtig für std::remove_cv_t + +namespace ntx +{ + + /** + * @brief Tiefensuche-Iterator für NtxINode Baumstrukturen + */ + template + struct NtxNodeDepthFirstIterator + { + // C++20 Concepts Support: + // Wir definieren iterator_concept, um explizit Forward Iterator Verhalten zu signalisieren. + using iterator_concept = std::forward_iterator_tag; + using iterator_category = std::forward_iterator_tag; + + using difference_type = std::ptrdiff_t; + + // FIX 1: value_type darf NIE const sein, auch wenn T const ist! + using value_type = std::remove_cv_t; + + using pointer = T*; + using reference = T&; + + NtxNodeDepthFirstIterator() + : m_root{ nullptr }, m_current{ nullptr } + { + } + + explicit NtxNodeDepthFirstIterator(pointer node) + : m_root{ node }, m_current{ node } + { + if (m_current) { + m_stack.push({ m_current, 0 }); + } + } + + pointer get() const + { + return m_current; + } + + int level() const + { + return static_cast(m_stack.size()) - 1; + } + + // FIX 2: Dereferenzierung muss const sein + pointer operator->() const + { + return m_current; + } + + // FIX 2: Dereferenzierung muss const sein + reference operator*() const + { + return *m_current; + } + + explicit operator bool() const + { + return m_current != nullptr; + } + + NtxNodeDepthFirstIterator& operator++() + { + if (!m_current || m_stack.empty()) { + m_current = nullptr; + return *this; + } + + while (!m_stack.empty()) { + auto& top = m_stack.top(); + auto node = top.first; + auto& nextChildIndex = top.second; + + // node->getChildCount() funktioniert, da T (bzw. ntx::NtxINode) hier vollständig bekannt ist + const auto childCount = node->getChildCount(); + while (nextChildIndex < childCount) { + auto child = node->getChild(nextChildIndex++); + if (child) { + m_current = child.get(); + m_stack.push({ m_current, 0 }); + return *this; + } + } + + m_stack.pop(); + } + + m_current = nullptr; + return *this; + } + + NtxNodeDepthFirstIterator operator++(int) + { + NtxNodeDepthFirstIterator tmp = *this; + ++(*this); + return tmp; + } + + // Hidden Friends für Vergleiche (ADL) + friend bool operator==(const NtxNodeDepthFirstIterator& a, const NtxNodeDepthFirstIterator& b) + { + return a.m_current == b.m_current; + } + + friend bool operator!=(const NtxNodeDepthFirstIterator& a, const NtxNodeDepthFirstIterator& b) + { + return a.m_current != b.m_current; + } + + private: + + pointer m_root; + pointer m_current; + std::stack> m_stack; + }; + +} // namespace ntx + +#endif // NTXNODE_ITERATORS_H \ No newline at end of file diff --git a/nodes/nodes/ntxnodeproxy.h b/nodes/nodes/ntxnodeproxy.h new file mode 100644 index 0000000..a4230d5 --- /dev/null +++ b/nodes/nodes/ntxnodeproxy.h @@ -0,0 +1,57 @@ +#ifndef NTX_NODEPROXY_H +#define NTX_NODEPROXY_H + +#include + +namespace ntx +{ + + /** + * @brief Transparenter Proxy für NtxINodePtr. + * + * operator->() → delegiert ALLE Methodenaufrufe an den internen Pointer. + * operator NtxINodePtr() → impliziter Cast wo NtxINodePtr erwartet wird. + * + * Keine einzige NtxINode-Methode muss reimplementiert werden. + */ + class NtxNodeProxy + { + public: + + explicit NtxNodeProxy(NtxINodePtr node) + : m_node(std::move(node)) + {} + + // ----------------------------------------------------------------------- + // Das Herzstück: leitet JEDEN Methodenaufruf transparent weiter + // proxy->getProperty("x") → m_node->getProperty("x") + // ----------------------------------------------------------------------- + NtxINode* operator->() { return m_node.get(); } + const NtxINode* operator->() const { return m_node.get(); } + + NtxINode& operator*() { return *m_node; } + const NtxINode& operator*() const { return *m_node; } + + // ----------------------------------------------------------------------- + // Implizite Konvertierung — überall wo NtxINodePtr erwartet wird + // ----------------------------------------------------------------------- + operator NtxINodePtr() const { return m_node; } + operator NtxNodeCPtr() const { return m_node; } + operator bool() const { return m_node != nullptr; } + + // ----------------------------------------------------------------------- + // Expliziter Zugriff + // ----------------------------------------------------------------------- + NtxINodePtr get() const { return m_node; } + + void reset(NtxINodePtr node = nullptr) { m_node = std::move(node); } + + private: + + NtxINodePtr m_node; + + }; + +} // namespace ntx + +#endif // NTX_NODEPROXY_H \ No newline at end of file diff --git a/nodes/nodes/ntxsharedpayload.cpp b/nodes/nodes/ntxsharedpayload.cpp new file mode 100644 index 0000000..f5cf12c --- /dev/null +++ b/nodes/nodes/ntxsharedpayload.cpp @@ -0,0 +1,150 @@ +#include + +namespace ntx +{ + + // ========================================================================= + // NtxSharedPayload: Key-basiert + // ========================================================================= + + NtxSharedPayload::NtxSharedPayload(const NtxPayloadPtr& original) + : m_originalPayload(original) + { + } + + bool NtxSharedPayload::hasValue(const NtxString& key) const + { + return m_originalPayload && m_originalPayload->hasValue(key); + } + + void NtxSharedPayload::setValue(const NtxString& key, const NtxVariant& value) + { + if (m_originalPayload) + m_originalPayload->setValue(key, value); + } + + NtxVariant NtxSharedPayload::getValue(const NtxString& key) const + { + if (m_originalPayload) + return m_originalPayload->getValue(key); + return NtxVariant{}; + } + + void NtxSharedPayload::removeValue(const NtxString& key) + { + if (m_originalPayload) + m_originalPayload->removeValue(key); + } + + void NtxSharedPayload::clearValues() + { + if (m_originalPayload) + m_originalPayload->clearValues(); + } + + size_t NtxSharedPayload::getValueCount() const + { + return m_originalPayload ? m_originalPayload->getValueCount() : 0; + } + + void NtxSharedPayload::forEachProperty(NtxPayloadVisitor visitor) const + { + if (m_originalPayload) + m_originalPayload->forEachProperty(visitor); + } + + /* + NtxStringList NtxSharedPayload::getValueKeys() const + { + if (m_originalPayload) + return m_originalPayload->getValueKeys(); + return {}; + } + */ + + // ========================================================================= + // NtxSharedPayload: Index-basiert + // ========================================================================= + + bool NtxSharedPayload::hasValue(size_t index) const + { + return m_originalPayload && m_originalPayload->hasValue(index); + } + + void NtxSharedPayload::setValue(size_t index, const NtxVariant& value) + { + if (m_originalPayload) + m_originalPayload->setValue(index, value); + } + + NtxVariant NtxSharedPayload::getValue(size_t index) const + { + if (m_originalPayload) + return m_originalPayload->getValue(index); + return NtxVariant{}; + } + + // ========================================================================= + // NtxSharedPayload: Iteratoren + // ========================================================================= + + /* + NtxPayloadIterator NtxSharedPayload::valuesBegin() + { + if (m_originalPayload) + return m_originalPayload->valuesBegin(); + // Leerer Iterator: Kein gültiger Fallback möglich → UB vermeiden + static std::vector empty; + return empty.begin(); + } + + NtxPayloadIterator NtxSharedPayload::valuesEnd() + { + if (m_originalPayload) + return m_originalPayload->valuesEnd(); + static std::vector empty; + return empty.end(); + } + + NtxPayloadCIterator NtxSharedPayload::valuesBegin() const + { + if (m_originalPayload) + return m_originalPayload->valuesBegin(); + static const std::vector empty; + return empty.cbegin(); + } + + NtxPayloadCIterator NtxSharedPayload::valuesEnd() const + { + if (m_originalPayload) + return m_originalPayload->valuesEnd(); + static const std::vector empty; + return empty.cend(); + } + */ + + // ========================================================================= + // NtxSharedPayload: Sonstiges + // ========================================================================= + + NtxPayloadPtr NtxSharedPayload::getOriginalPayload() const + { + return m_originalPayload; + } + + void NtxSharedPayload::setOriginalPayload(const NtxPayloadPtr& original) + { + m_originalPayload = original; + } + + bool NtxSharedPayload::isValid() const + { + return m_originalPayload != nullptr; + } + + void NtxSharedPayload::detach() + { + m_originalPayload.reset(); + } + +} // namespace ntx \ No newline at end of file diff --git a/nodes/nodes/ntxtaggregatenode.h b/nodes/nodes/ntxtaggregatenode.h new file mode 100644 index 0000000..5247c09 --- /dev/null +++ b/nodes/nodes/ntxtaggregatenode.h @@ -0,0 +1,89 @@ +#ifndef NTX_AGGREGATENODE_H +#define NTX_AGGREGATENODE_H + +#include +#include + +namespace ntx +{ + + /** + * @brief Implementierung via Aggregation (KISS-Variante). + * + * Besitzt oder referenziert einen NtxIPayload über shared_ptr. + * Owning: NtxTAggregateNode(std::make_shared(...)) + * Referencing: NtxTAggregateNode(existingPayloadPtr) + */ + template + class NtxTAggregateNode : public NtxIBasicNode + { + public: + + // Owning: erstellt eigene TPayloadImpl-Instanz + /* + template + explicit NtxTAggregateNode(Args&&... args) + : NtxIBasicNode{} + , m_payload(std::make_shared(std::forward(args)...)) + {} + */ + + NtxTAggregateNode() + : NtxIBasicNode{}, m_payload(std::make_shared()) + { + + } + + // Referencing: verknüpft bestehenden Payload + explicit NtxTAggregateNode(std::shared_ptr sharedPayload) + : NtxIBasicNode{} + , m_payload(std::move(sharedPayload)) + {} + + // Payload zur Laufzeit austauschen (Owning ↔ Referencing) + void setPayload(NtxPayloadPtr payload) { m_payload = std::move(payload); } + NtxPayloadPtr getPayload() const { return m_payload; } + + // ======================================== + // NtxIPayload Interface: Delegation + // ======================================== + + size_t getPropertyCount() const override { return m_payload->getPropertyCount(); } + void clearProperties() override { m_payload->clearProperties(); } + + bool hasProperty(const NtxString& key) const override { return m_payload->hasProperty(key); } + void setProperty(const NtxString& key, const NtxVariant& value) override + { m_payload->setProperty(key, value); } + NtxVariant getProperty(const NtxString& key) const override + { return m_payload->getProperty(key); } + void removeProperty(const NtxString& key) override { m_payload->removeProperty(key); } + + bool hasProperty(size_t index) const override { return m_payload->hasProperty(index); } + void setProperty(size_t index, const NtxVariant& value) override + { m_payload->setProperty(index, value); } + NtxVariant getProperty(size_t index) const override { return m_payload->getProperty(index); } + + void forEachProperty(NtxPayloadVisitor visitor) const override + { + //m_payload->forEachProperty(visitor); + } + + NtxNodePtr clone() const override + { + // m_payload ist bereits shared_ptr — kein cast nötig + auto newNode = std::make_shared>( + std::make_shared(*m_payload)); + newNode->setClassTypeId(this->getClassTypeId()); + return newNode; + } + + private: + + // NtxPayloadPtr m_payload; // Nein! das ist eine völlig unnötige Einschränkung auf IPayload. + std::shared_ptr m_payload; + + }; + +} // namespace ntx + +#endif // NTX_AGGREGATENODE_H \ No newline at end of file diff --git a/nodes/nodes/ntxtcloneable.h b/nodes/nodes/ntxtcloneable.h new file mode 100644 index 0000000..773f111 --- /dev/null +++ b/nodes/nodes/ntxtcloneable.h @@ -0,0 +1,34 @@ +#ifndef NTX_CLONEABLE_H +#define NTX_CLONEABLE_H + +#include +#include + +namespace ntx +{ + + /** + * @brief CRTP-Mixin: generiert clone() automatisch für jede Subklasse. + * + * Voraussetzung: Derived ist copy-konstruierbar. + * + * Verwendung: + * class MyNode : public NtxTCloneable> { ... }; + */ + template + class NtxTCloneable : public Base + { + public: + + using Base::Base; // Konstruktoren durchreichen + + NtxNodePtr clone() const override + { + return std::make_shared(static_cast(*this)); + } + + }; + +} // namespace ntx + +#endif // NTX_CLONEABLE_H \ No newline at end of file diff --git a/nodes/nodes/ntxtmixinnode.h b/nodes/nodes/ntxtmixinnode.h new file mode 100644 index 0000000..b400a15 --- /dev/null +++ b/nodes/nodes/ntxtmixinnode.h @@ -0,0 +1,73 @@ +#ifndef NTX_TMIXINNODE_H +#define NTX_TMIXINNODE_H + +#include +#include +#include +#include + +#include + +namespace ntx +{ + + /** + * @brief Implementierung via Vererbung (Mixin). + * + * TPayloadImpl wird als Basisklasse eingemischt. + */ + template + class NtxTMixInNode : public NtxIBasicNode, public TPayloadImpl + { + + public: + + // Konstruktor forwarding an PayloadImpl + template + explicit NtxTMixInNode(Args&&... args) + : NtxIBasicNode{}, + TPayloadImpl{std::forward(args)...} + { + } + + // ======================================== + // NtxINode Interface: Payload Forwarding + // ======================================== + + size_t getPropertyCount() const override { return TPayloadImpl::getPropertyCount(); } + void clearProperties() override { TPayloadImpl::clearProperties(); } + + bool hasProperty(const NtxString& key) const override { return TPayloadImpl::hasProperty(key); } + void setProperty(const NtxString& key, const NtxVariant& value) override { TPayloadImpl::setProperty(key, value); } + NtxVariant getProperty(const NtxString& key) const override { return TPayloadImpl::getProperty(key); } + void removeProperty(const NtxString& key) override { TPayloadImpl::removeProperty(key); } + + bool hasProperty(size_t index) const override { return TPayloadImpl::hasProperty(index); } + void setProperty(size_t index, const NtxVariant& value) override { TPayloadImpl::setProperty(index, value); } + NtxVariant getProperty(size_t index) const override { return TPayloadImpl::getProperty(index); } + + + // Deducing This Support (Raw Access) + //std::vector& doGetValues() override { return TPayloadImpl::doGetValues(); } + //const std::vector& doGetValues() const override { return TPayloadImpl::doGetValues(); } + + + void forEachProperty(NtxPayloadVisitor visitor) const override + { + //TPayloadImpl::forEachProperty(visitor); + } + + + NtxNodePtr clone() const override + { + // Basic Clone Implementation: Kopiert PayloadImpl Teil via Copy-Ctor + auto newNode = std::make_shared>(*static_cast(this)); + // Base Properties kopieren + newNode->setClassTypeId(this->getClassTypeId()); + return newNode; + } + }; + +} // namespace ntx + +#endif // NTX_TMIXINNODE_H \ No newline at end of file diff --git a/nodes/services/ntxclipboard.cpp b/nodes/services/ntxclipboard.cpp new file mode 100644 index 0000000..35d8236 --- /dev/null +++ b/nodes/services/ntxclipboard.cpp @@ -0,0 +1,83 @@ +#include "ntxclipboard.h" + +namespace ntx +{ + void NtxClipboard::copyNodes(const NtxNodeList& nodes) + { + m_nodeEntryList.clear(); + + for (const auto& node : nodes) + { + if (!node) continue; + + NtxNodeEntry entry; + entry.clone = createOrphanClone(node); + entry.originalParent = node->getParent(); + entry.originalPosition = node->ownPos().value_or(0); + + m_nodeEntryList.push_back(entry); + } + } + + void NtxClipboard::cutNodes(const NtxNodeList& nodes) + { + // Identisch zu copyNodes - Der Unterschied liegt in NtxHeadNode + copyNodes(nodes); + } + + NtxNodeList NtxClipboard::pasteNodes() const + { + NtxNodeList result; + + for (const auto& entry : m_nodeEntryList) + { + if (!entry.clone) continue; + + // Neuen Klon vom Clipboard-Clone erstellen (mehrfaches Paste möglich) + auto freshClone = entry.clone->clone(); + if (freshClone) + { + result.push_back(freshClone); + } + } + + return result; + } + + bool NtxClipboard::isEmpty() const + { + return m_nodeEntryList.empty(); + } + + size_t NtxClipboard::getCount() const + { + return m_nodeEntryList.size(); + } + + void NtxClipboard::clear() + { + m_nodeEntryList.clear(); + } + + const NtxNodeEntryList& NtxClipboard::getEntries() const + { + return m_nodeEntryList; + } + + NtxNodePtr NtxClipboard::createOrphanClone(NtxNodePtr node) const + { + if (!node) return nullptr; + + // Deep-Clone erstellen + auto clone = node->clone(); + + // Parent-Verbindung trennen (Elternlos machen) + if (clone) + { + clone->setParent(nullptr); + } + + return clone; + } + +} // namespace ntx \ No newline at end of file diff --git a/nodes/services/ntxclipboard.h b/nodes/services/ntxclipboard.h new file mode 100644 index 0000000..ffcc274 --- /dev/null +++ b/nodes/services/ntxclipboard.h @@ -0,0 +1,47 @@ +#ifndef NTX_CLIPBOARD_H +#define NTX_CLIPBOARD_H + +#include +#include + +namespace ntx +{ + /** + * @brief Einfaches Clipboard für Copy/Cut/Paste-Operationen. + * + * Speichert elternlose Deep-Clones mit Original-Position für Undo. + * Kein Singleton - wird als Member von NtxHeadNode verwendet. + */ + class NtxClipboard + { + + public: + + NtxClipboard() = default; + ~NtxClipboard() = default; + + // Copy/Cut Operations + void copyNodes(const NtxNodeList& nodes); + void cutNodes(const NtxNodeList& nodes); + + // Paste Operations + NtxNodeList pasteNodes() const; + + // State + bool isEmpty() const; + size_t getCount() const; + void clear(); + + // Access + const NtxNodeEntryList& getEntries() const; + + private: + + NtxNodeEntryList m_nodeEntryList; + + NtxNodePtr createOrphanClone(NtxNodePtr node) const; + }; + +} // namespace ntx + +#endif // NTX_CLIPBOARD_H \ No newline at end of file diff --git a/nodes/services/ntxcmdlist.cpp b/nodes/services/ntxcmdlist.cpp new file mode 100644 index 0000000..3ae3cf8 --- /dev/null +++ b/nodes/services/ntxcmdlist.cpp @@ -0,0 +1,141 @@ +#include + +namespace ntx +{ + + NtxCmdList::NtxCmdList() + : m_executedCount(0) + { + setDescription("Multi Command"); + } + + NtxCmdList::NtxCmdList(const NtxString& description) + : m_executedCount(0) + { + setDescription(description); + } + + bool NtxCmdList::execute() + { + m_executedCount = 0; + + for (size_t i = 0; i < m_commands.size(); ++i) + { + if (!m_commands[i]->canExecute()) + { + rollback(i); + return false; + } + + if (!m_commands[i]->execute()) + { + rollback(i); + return false; + } + + m_executedCount++; + } + + return true; + } + + bool NtxCmdList::undo() + { + if (m_executedCount == 0) + return false; + + for (size_t i = m_executedCount; i > 0; --i) + { + size_t index = i - 1; + + if (!m_commands[index]->canUndo()) + continue; + + m_commands[index]->undo(); + } + + m_executedCount = 0; + return true; + } + + bool NtxCmdList::canExecute() const + { + if (m_commands.empty()) + return false; + + for (const auto& cmd : m_commands) + { + if (!cmd->canExecute()) + return false; + } + + return true; + } + + bool NtxCmdList::canUndo() const + { + if (m_executedCount == 0) + return false; + + for (size_t i = 0; i < m_executedCount; ++i) + { + if (m_commands[i]->canUndo()) + return true; + } + + return false; + } + + NtxString NtxCmdList::getDescription() const + { + NtxString desc = NtxICommand::getDescription(); + + if (!desc.empty()) + return desc; + + if (m_commands.empty()) + return "Empty Multi Command"; + + return "Multi Command (" + std::to_string(m_commands.size()) + " commands)"; + } + + void NtxCmdList::addCommand(NtxCommandUPtr command) + { + if (command) + { + m_commands.push_back(std::move(command)); + } + } + + void NtxCmdList::clear() + { + m_commands.clear(); + m_executedCount = 0; + } + + size_t NtxCmdList::count() const + { + return m_commands.size(); + } + + bool NtxCmdList::isEmpty() const + { + return m_commands.empty(); + } + + void NtxCmdList::rollback(size_t upToIndex) + { + for (size_t i = upToIndex; i > 0; --i) + { + size_t index = i - 1; + + if (m_commands[index]->canUndo()) + { + m_commands[index]->undo(); + } + } + + m_executedCount = 0; + } + +} // namespace ntx \ No newline at end of file diff --git a/nodes/services/ntxcmdlist.h b/nodes/services/ntxcmdlist.h new file mode 100644 index 0000000..99b0d2b --- /dev/null +++ b/nodes/services/ntxcmdlist.h @@ -0,0 +1,42 @@ +#ifndef NTX_CMDLIST_H +#define NTX_CMDLIST_H + +#include +#include + +namespace ntx +{ + + class NtxCmdList : public NtxICommand + { + public: + + NtxCmdList(); + explicit NtxCmdList(const NtxString& description); + ~NtxCmdList() override = default; + + bool execute() override; + bool undo() override; + + bool canExecute() const override; + bool canUndo() const override; + + NtxString getDescription() const override; + + void addCommand(NtxCommandUPtr command); + void clear(); + + size_t count() const; + bool isEmpty() const; + + private: + + std::vector m_commands; + size_t m_executedCount; + + void rollback(size_t upToIndex); + }; + +} // namespace ntx + +#endif // NTX_CMDLIST_H \ No newline at end of file diff --git a/nodes/services/ntxheadnode.cpp b/nodes/services/ntxheadnode.cpp new file mode 100644 index 0000000..77aa20c --- /dev/null +++ b/nodes/services/ntxheadnode.cpp @@ -0,0 +1,55 @@ +#include + +namespace ntx +{ + + bool NtxHeadNode::copyNode(NtxNodePtr node) + { + return false; + } + + bool NtxHeadNode::cutNode(NtxNodePtr node) + { + return false; + } + + NtxNodePtr NtxHeadNode::pasteNode(NtxNodePtr parent, int index) + { + return nullptr; + } + + bool NtxHeadNode::addNode(NtxNodePtr parent, NtxNodePtr node, int index) + { + return false; + } + + bool NtxHeadNode::deleteNode(NtxNodePtr node) + { + return false; + } + + bool NtxHeadNode::hasClipboard() const + { + return false; + } + + void NtxHeadNode::clearClipboard() + { + } + + NtxNodePtr NtxHeadNode::peekClipboard() const + { + return nullptr; + } + + bool NtxHeadNode::validateNode(NtxNodePtr node) const + { + return false; + } + + bool NtxHeadNode::canAddToParent(NtxNodePtr parent, NtxNodePtr child) const + { + return false; + } + +} // namespace ntx \ No newline at end of file diff --git a/nodes/services/ntxheadnode.h b/nodes/services/ntxheadnode.h new file mode 100644 index 0000000..62075bb --- /dev/null +++ b/nodes/services/ntxheadnode.h @@ -0,0 +1,46 @@ +#ifndef NTX_HEADNODE_H +#define NTX_HEADNODE_H + +#include +#include +#include + +namespace ntx +{ + class NtxINode; + using NtxNodePtr = std::shared_ptr; + + class NtxHeadNode + { + public: + + NtxHeadNode() = default; + ~NtxHeadNode() = default; + + NtxHeadNode(const NtxHeadNode&) = delete; + NtxHeadNode& operator=(const NtxHeadNode&) = delete; + NtxHeadNode(NtxHeadNode&&) = default; + NtxHeadNode& operator=(NtxHeadNode&&) = default; + + bool copyNode(NtxNodePtr node); + bool cutNode(NtxNodePtr node); + NtxNodePtr pasteNode(NtxNodePtr parent, int index = -1); + bool addNode(NtxNodePtr parent, NtxNodePtr node, int index = -1); + bool deleteNode(NtxNodePtr node); + + bool hasClipboard() const; + void clearClipboard(); + NtxNodePtr peekClipboard() const; + + private: + + NtxClipboard m_clipboard; // ← Eigene Klasse als Member! + bool m_isCutOperation{false}; + + bool validateNode(NtxNodePtr node) const; + bool canAddToParent(NtxNodePtr parent, NtxNodePtr child) const; + }; + +} // namespace ntx + +#endif // NTX_HEADNODE_H \ No newline at end of file diff --git a/nodes/services/ntxicommand.cpp b/nodes/services/ntxicommand.cpp new file mode 100644 index 0000000..cde0cc6 --- /dev/null +++ b/nodes/services/ntxicommand.cpp @@ -0,0 +1,21 @@ +#include + +namespace ntx +{ + + bool NtxICommand::canUndo() const + { + return true; + } + + NtxString NtxICommand::getDescription() const + { + return m_description; + } + + void NtxICommand::setDescription(const NtxString& description) + { + m_description = description; + } + +} // namespace ntx \ No newline at end of file diff --git a/nodes/services/ntxicommand.h b/nodes/services/ntxicommand.h new file mode 100644 index 0000000..0c9a545 --- /dev/null +++ b/nodes/services/ntxicommand.h @@ -0,0 +1,52 @@ +#ifndef NTX_ICOMMAND_H +#define NTX_ICOMMAND_H + +#include +#include + +namespace ntx +{ + + // Einfache Struct für Clipboard-Eintrag + struct NtxNodeEntry + { + NtxNodePtr clone; // Elternloser Deep-Clone + NtxNodeWeakPtr originalParent; // Schwache Referenz für Undo + size_t originalPosition; // Position im Original-Parent + }; + + using NtxNodeEntryList = std::vector; + + class NtxICommand + { + public: + + virtual ~NtxICommand() = default; + + virtual bool execute() = 0; + virtual bool undo() = 0; + + virtual bool canExecute() const = 0; + virtual bool canUndo() const; + + virtual NtxString getDescription() const; + + protected: + + NtxICommand() = default; + NtxICommand(const NtxICommand&) = default; + NtxICommand& operator=(const NtxICommand&) = default; + + void setDescription(const NtxString& description); + + private: + + NtxString m_description; + }; + + using NtxCommandPtr = std::shared_ptr; + using NtxCommandUPtr = std::unique_ptr; + +} // namespace ntx + +#endif // NTX_ICOMMAND_H \ No newline at end of file diff --git a/nodes/services/ntxnodecommands.cpp b/nodes/services/ntxnodecommands.cpp new file mode 100644 index 0000000..5451983 --- /dev/null +++ b/nodes/services/ntxnodecommands.cpp @@ -0,0 +1,161 @@ +#include "ntxnodecommands.h" + +namespace ntx +{ + + // ========================================================================= + // NtxInsertNodeCmd + // ========================================================================= + + NtxInsertNodeCmd::NtxInsertNodeCmd(NtxNodePtr parent, NtxNodePtr node, size_t index) + : m_parent(std::move(parent)) + , m_node(std::move(node)) + , m_index(index) + { + setDescription("Insert node"); + } + + bool NtxInsertNodeCmd::execute() + { + if (!canExecute()) + return false; + + if (m_index > m_parent->getChildCount()) + m_index = m_parent->getChildCount(); + + m_parent->insertChild(m_index, m_node); + m_executed = true; + return true; + } + + bool NtxInsertNodeCmd::undo() + { + if (!m_executed) + return false; + + auto idx = m_parent->getChildIndex(m_node); + if (idx == static_cast(-1)) + return false; + + m_parent->removeChild(idx); + m_executed = false; + return true; + } + + bool NtxInsertNodeCmd::canExecute() const + { + return m_parent && m_node && !m_executed; + } + + // ========================================================================= + // NtxRemoveNodeCmd + // ========================================================================= + + NtxRemoveNodeCmd::NtxRemoveNodeCmd(NtxNodePtr node) + : m_node(std::move(node)) + { + setDescription("Remove node"); + } + + bool NtxRemoveNodeCmd::execute() + { + if (!canExecute()) + return false; + + m_parent = m_node->getParent(); + if (!m_parent) + return false; + + auto pos = m_node->ownPos(); + if (!pos) + return false; + + m_index = *pos; + m_parent->removeChild(m_index); + m_executed = true; + return true; + } + + bool NtxRemoveNodeCmd::undo() + { + if (!m_executed || !m_parent) + return false; + + m_parent->insertChild(m_index, m_node); + m_executed = false; + return true; + } + + bool NtxRemoveNodeCmd::canExecute() const + { + return m_node && m_node->getParent() && !m_executed; + } + + // ========================================================================= + // NtxMacroCommand + // ========================================================================= + + NtxMacroCommand::NtxMacroCommand(const NtxString& description) + { + setDescription(description); + } + + NtxCommandUPtr NtxMacroCommand::create( + const NtxString& description, + std::vector commands) + { + auto macro = std::make_unique(description); + for (auto& cmd : commands) + macro->add(std::move(cmd)); + return macro; + } + + void NtxMacroCommand::add(NtxCommandUPtr cmd) + { + if (cmd) + m_commands.push_back(std::move(cmd)); + } + + bool NtxMacroCommand::execute() + { + m_executedCount = 0; + + for (auto& cmd : m_commands) + { + if (!cmd->execute()) + { + // Rollback: Bereits ausgeführte rückgängig machen + for (size_t i = m_executedCount; i > 0; --i) + m_commands[i - 1]->undo(); + m_executedCount = 0; + return false; + } + ++m_executedCount; + } + return true; + } + + bool NtxMacroCommand::undo() + { + for (size_t i = m_executedCount; i > 0; --i) + { + if (!m_commands[i - 1]->undo()) + return false; + } + m_executedCount = 0; + return true; + } + + bool NtxMacroCommand::canExecute() const + { + if (m_commands.empty()) + return false; + for (const auto& cmd : m_commands) + { + if (!cmd->canExecute()) + return false; + } + return true; + } + +} // namespace ntx \ No newline at end of file diff --git a/nodes/services/ntxnodecommands.h b/nodes/services/ntxnodecommands.h new file mode 100644 index 0000000..64550a6 --- /dev/null +++ b/nodes/services/ntxnodecommands.h @@ -0,0 +1,102 @@ +#ifndef NTX_NODECOMMANDS_H +#define NTX_NODECOMMANDS_H + +#include + +namespace ntx +{ + + // ========================================================================= + // Atomar: Knoten einfügen + // ========================================================================= + + /** + * @brief Fügt einen Knoten an einer Position ein. + * Undo entfernt ihn wieder. + */ + class NtxInsertNodeCmd : public NtxICommand + { + + public: + + NtxInsertNodeCmd(NtxNodePtr parent, NtxNodePtr node, size_t index); + + bool execute() override; + bool undo() override; + bool canExecute() const override; + + private: + + NtxNodePtr m_parent; + NtxNodePtr m_node; + size_t m_index; + bool m_executed{false}; + }; + + // ========================================================================= + // Atomar: Knoten entfernen + // ========================================================================= + + /** + * @brief Entfernt einen Knoten. Merkt sich Parent + Position für Undo. + */ + class NtxRemoveNodeCmd : public NtxICommand + { + + public: + + explicit NtxRemoveNodeCmd(NtxNodePtr node); + + bool execute() override; + bool undo() override; + bool canExecute() const override; + + private: + + NtxNodePtr m_node; + NtxNodePtr m_parent; + size_t m_index{0}; + bool m_executed{false}; + }; + + // ========================================================================= + // Makro-Command (Composite Command) + // ========================================================================= + + /** + * @brief Führt mehrere Commands als atomare Einheit aus. + * + * execute(): Alle Commands vorwärts ausführen. + * undo(): Alle Commands rückwärts rückgängig machen. + * Bei Fehler: Rollback der bereits ausgeführten. + */ + class NtxMacroCommand : public NtxICommand + { + + public: + + NtxMacroCommand() = default; + explicit NtxMacroCommand(const NtxString& description); + + /// Factory: Erzeugt ein Makro aus einer Liste von Commands. + static NtxCommandUPtr create( + const NtxString& description, + std::vector commands); + + void add(NtxCommandUPtr cmd); + + bool execute() override; + bool undo() override; + bool canExecute() const override; + + size_t commandCount() const { return m_commands.size(); } + + private: + + std::vector m_commands; + size_t m_executedCount{0}; + }; + +} // namespace ntx + +#endif // NTX_NODECOMMANDS_H \ No newline at end of file diff --git a/nodes/services/ntxnodetree.cpp b/nodes/services/ntxnodetree.cpp new file mode 100644 index 0000000..f98bf42 --- /dev/null +++ b/nodes/services/ntxnodetree.cpp @@ -0,0 +1,209 @@ +#include + +#include +#include + + +namespace ntx +{ + + NtxNodeTree::NtxNodeTree(NtxClassTypeId id) + { + //m_ctypeId = id; + } + + NtxNodePtr NtxNodeTree::clone() const + { + /* + auto cloned = NtxNodeFactory::makeNode(getClassTypeLabel()); + qDebug() << "Cloning NtxNodeTree with CTypeLabel: NOT DONE: " << getClassTypeLabel().c_str(); + + forEachProperty([&](const NtxString& key, const NtxVariant& value) { + cloned->setProperty(key, value); + }); + for (size_t i = 0; i < getChildCount(); ++i) + cloned->addChild(getChild(i)->clone()); + + + + + return cloned; + + */ + + return nullptr; + } + + // ========================================================================= + // Command-Ausführung (delegiert an NtxUndoStack) + // ========================================================================= + + bool NtxNodeTree::execute(NtxCommandUPtr cmd) + { + if (!cmd || !cmd->canExecute()) + return false; + m_undoStack.push(std::move(cmd)); + return true; + } + + bool NtxNodeTree::undo() + { + if (!canUndo()) + return false; + m_undoStack.undo(); + return true; + } + + bool NtxNodeTree::redo() + { + if (!canRedo()) + return false; + m_undoStack.redo(); + return true; + } + + bool NtxNodeTree::canUndo() const { return m_undoStack.canUndo(); } + bool NtxNodeTree::canRedo() const { return m_undoStack.canRedo(); } + NtxString NtxNodeTree::undoText() const { return m_undoStack.undoText(); } + NtxString NtxNodeTree::redoText() const { return m_undoStack.redoText(); } + + void NtxNodeTree::setClean() { m_undoStack.setClean(); } + bool NtxNodeTree::isDirty() const { return !m_undoStack.isClean(); } + + // ========================================================================= + // Einzel-Operationen + // ========================================================================= + + bool NtxNodeTree::addNode(NtxNodePtr parent, NtxNodePtr node, size_t index) + { + return execute(std::make_unique( + std::move(parent), std::move(node), index)); + } + + bool NtxNodeTree::deleteNode(NtxNodePtr node) + { + return execute(std::make_unique(std::move(node))); + } + + bool NtxNodeTree::moveNode(NtxNodePtr node, NtxNodePtr newParent, size_t newIndex) + { + // Move = Remove + Insert als Makro + auto macro = std::make_unique("Move node"); + macro->add(std::make_unique(node)); + macro->add(std::make_unique( + std::move(newParent), std::move(node), newIndex)); + return execute(std::move(macro)); + } + + // ========================================================================= + // Listen-Operationen (Makro-Commands) + // ========================================================================= + + bool NtxNodeTree::addNodes(NtxNodePtr parent, const NtxNodeList& nodes, size_t index) + { + std::vector cmds; + cmds.reserve(nodes.size()); + + for (size_t i = 0; i < nodes.size(); ++i) + { + cmds.push_back(std::make_unique( + parent, nodes[i], index + i)); + } + + return execute(NtxMacroCommand::create( + "Insert " + std::to_string(nodes.size()) + " nodes", + std::move(cmds))); + } + + bool NtxNodeTree::deleteNodes(const NtxNodeList& nodes) + { + std::vector cmds; + cmds.reserve(nodes.size()); + + // Rückwärts löschen → Indizes bleiben stabil + for (auto it = nodes.rbegin(); it != nodes.rend(); ++it) + cmds.push_back(std::make_unique(*it)); + + return execute(NtxMacroCommand::create( + "Delete " + std::to_string(nodes.size()) + " nodes", + std::move(cmds))); + } + + // ========================================================================= + // Clipboard + // ========================================================================= + + void NtxNodeTree::copyNodes(const NtxNodeList& nodes) + { + m_clipboard.copyNodes(nodes); + m_isCutOperation = false; + } + + void NtxNodeTree::cutNodes(const NtxNodeList& nodes) + { + m_clipboard.copyNodes(nodes); + m_isCutOperation = true; + } + + NtxNodeList NtxNodeTree::pasteNodes(NtxNodePtr parent, size_t index) + { + NtxNodeList pasted = m_clipboard.pasteNodes(); + if (pasted.empty()) + return {}; + + if (m_isCutOperation) + { + // Cut+Paste = Remove originals + Insert clones (als Makro) + auto macro = std::make_unique( + "Paste " + std::to_string(pasted.size()) + " nodes (cut)"); + + // 1. Originale entfernen (rückwärts für stabile Indizes) + const auto& entries = m_clipboard.getEntries(); + for (auto it = entries.rbegin(); it != entries.rend(); ++it) + { + if (auto origParent = it->originalParent.lock()) + { + if (it->originalPosition < origParent->getChildCount()) + { + auto origNode = origParent->getChild(it->originalPosition); + if (origNode) + macro->add(std::make_unique(origNode)); + } + } + } + + // 2. Clones einfügen + for (size_t i = 0; i < pasted.size(); ++i) + { + macro->add(std::make_unique( + parent, pasted[i], index + i)); + } + + if (!execute(std::move(macro))) + return {}; + + m_isCutOperation = false; + m_clipboard.clear(); + } + else + { + // Copy+Paste = nur Insert + if (!addNodes(parent, pasted, index)) + return {}; + } + + return pasted; + } + + bool NtxNodeTree::hasClipboard() const + { + return !m_clipboard.isEmpty(); + } + + void NtxNodeTree::clearClipboard() + { + m_clipboard.clear(); + m_isCutOperation = false; + } + +} // namespace ntx \ No newline at end of file diff --git a/nodes/services/ntxnodetree.h b/nodes/services/ntxnodetree.h new file mode 100644 index 0000000..ef8e856 --- /dev/null +++ b/nodes/services/ntxnodetree.h @@ -0,0 +1,99 @@ +#ifndef NTX_NODETREE_H +#define NTX_NODETREE_H + +#include +#include +#include +#include + +namespace ntx +{ + + /** + * @brief Service-Schicht für Knoten-Operationen mit Undo/Redo. + * + * Ist selbst ein NtxINode (Composite-Pattern): Kann als Wurzel + * eines Teilbaums dienen und in andere NtxNodeTrees eingehängt werden. + * + * Bietet: + * - Command-basierte Baum-Manipulation (add/delete/move) + * - Undo/Redo via NtxUndoStack + * - Copy/Cut/Paste via NtxClipboard + * - Listen-Operationen via NtxMacroCommand + */ + class NtxNodeTree //: public NtxNode + { + friend class NtxNodeFactory; + + public: + + //~NtxNodeTree() override = default; + + NtxNodeTree(const NtxNodeTree&) = delete; + NtxNodeTree& operator=(const NtxNodeTree&) = delete; + + NtxNodePtr clone() const;// override; + + // ========================================================================= + // Command-Ausführung + // ========================================================================= + + /// Führt ein beliebiges Command aus (mit Undo-Support). + bool execute(NtxCommandUPtr cmd); + + bool undo(); + bool redo(); + bool canUndo() const; + bool canRedo() const; + NtxString undoText() const; + NtxString redoText() const; + + void setClean(); + bool isDirty() const; + + // ========================================================================= + // Einzel-Operationen (erzeugen intern Commands) + // ========================================================================= + + bool addNode(NtxNodePtr parent, NtxNodePtr node, size_t index); + bool deleteNode(NtxNodePtr node); + bool moveNode(NtxNodePtr node, NtxNodePtr newParent, size_t newIndex); + + // ========================================================================= + // Listen-Operationen (erzeugen NtxMacroCommand) + // ========================================================================= + + bool addNodes(NtxNodePtr parent, const NtxNodeList& nodes, size_t index); + bool deleteNodes(const NtxNodeList& nodes); + + // ========================================================================= + // Clipboard + // ========================================================================= + + void copyNodes(const NtxNodeList& nodes); + void cutNodes(const NtxNodeList& nodes); + NtxNodeList pasteNodes(NtxNodePtr parent, size_t index); + + bool hasClipboard() const; + void clearClipboard(); + + // ========================================================================= + // Zugriff auf Interna + // ========================================================================= + + NtxUndoStack& undoStack() { return m_undoStack; } + const NtxUndoStack& undoStack() const { return m_undoStack; } + + private: + + NtxNodeTree() = default; + explicit NtxNodeTree(NtxClassTypeId id); + + NtxUndoStack m_undoStack; + NtxClipboard m_clipboard; + bool m_isCutOperation{false}; + }; + +} // namespace ntx + +#endif // NTX_NODETREE_H \ No newline at end of file diff --git a/nodes/services/ntxnodexml.cpp b/nodes/services/ntxnodexml.cpp new file mode 100644 index 0000000..17bc03b --- /dev/null +++ b/nodes/services/ntxnodexml.cpp @@ -0,0 +1,209 @@ + + +#include "ntxnodexml.h" +#include +#include +#include +#include // Add this include at the top of the file + + +namespace ntx +{ + // Rekursive Hilfsfunktion + static NtxNodePtr parseElementRecursive(const pugi::xml_node& xmlNode) + { + if (!xmlNode) + return nullptr; + + // 1. Tag-Name als ID-String verwenden + NtxString tagName = xmlNode.name(); + + // Ignoriere spezielle Knotentypen (Kommentare, PCDATA/Text, etc.) wenn sie leer sind + // In diesem einfachen Beispiel behandeln wir nur Element-Knoten als echte NtxNodes. + if (xmlNode.type() != pugi::node_element) + return nullptr; + + // 2. Erzeuge Knoten via Factory (nutzt implizit CTypeFactory für Mapping) + NtxNodePtr newNode = NtxNodeFactory::makeNode(tagName); + if (!newNode) + return nullptr; + + // 3. Attribute übertragen + for (pugi::xml_attribute attr : xmlNode.attributes()) + { + NtxString key = attr.name(); + + // Einfache Heuristik für Typen: + // PugiXML liefert strings. Wir könnten versuchen zu konvertieren oder alles als String lassen. + // Hier: Alles als String speichern, oder versuchen int/double zu parsen. + // Fürs erste: Speichern als String, Payload erlaubt mixed types. + newNode->set(key, NtxString(attr.value())); + + /* Optional: Intelligentes Parsen + if (attr.as_int() != 0 || std::string(attr.value()) == "0") + newNode->set(key, attr.as_int()); + else + newNode->set(key, NtxString(attr.value())); + */ + } + + // 4. Kinder rekursiv verarbeiten + for (pugi::xml_node child : xmlNode.children()) + { + NtxNodePtr childNode = parseElementRecursive(child); + if (childNode) + { + newNode->addChild(childNode); + } + } + + return newNode; + } + + NtxNodePtr NtxNodeXml::loadFromFile(const std::string& filename) + { + pugi::xml_document doc; + pugi::xml_parse_result result = doc.load_file(filename.c_str()); + + if (!result) + { + std::cerr << "XML Load Error: " << result.description() << " in " << filename << std::endl; + return nullptr; + } + + // Wir nehmen den ersten Root-Knoten (das Wurzelelement) + // doc.document_element() gibt das erste Kind vom Typ Element zurück (ignoriert PI, DOCTYPE etc.) + return parseElementRecursive(doc.document_element()); + } + + NtxNodePtr NtxNodeXml::loadFromString(const std::string& xmlContent) + { + pugi::xml_document doc; + pugi::xml_parse_result result = doc.load_string(xmlContent.c_str()); + + if (!result) + { + std::cerr << "XML Parse Error: " << result.description() << std::endl; + return nullptr; + } + + return parseElementRecursive(doc.document_element()); + } + + // Rekursive Hilfsfunktion zum Serialisieren + static void serializeNodeRecursive(const NtxNodeCPtr& node, pugi::xml_node& xmlNode) + { + if (!node) + return; + + // 1. Tag-Name aus CType-Label holen + NtxString tagName = node->getClassTypeLabel(); + pugi::xml_node childElement = xmlNode.append_child(tagName.c_str()); + + // 2. Alle Attribute aus dem Payload schreiben + node->forEachProperty([&childElement](const NtxString& key, const NtxVariant& value) + { + // Konvertiere NtxVariant zu String und setze als XML-Attribut + std::visit([&childElement, &key](auto&& arg) + { + using T = std::decay_t; + + if constexpr (std::is_same_v) + { + // Überspringe leere Werte + } + else if constexpr (std::is_same_v) + { + childElement.append_attribute(key.c_str()).set_value(arg.c_str()); + } + else if constexpr (std::is_same_v) + { + childElement.append_attribute(key.c_str()).set_value(arg); + } + else if constexpr (std::is_same_v) + { + childElement.append_attribute(key.c_str()).set_value(arg); + } + else if constexpr (std::is_same_v) + { + childElement.append_attribute(key.c_str()).set_value(arg); + } + }, value); + }); + + // 3. Kinder rekursiv verarbeiten + for (size_t i = 0; i < node->getChildCount(); ++i) + { + NtxNodeCPtr child = node->getChild(i); + if (child) + { + serializeNodeRecursive(child, childElement); + } + } + } + + bool NtxNodeXml::saveToFile(const NtxNodePtr& node, const std::string& filename) + { + return saveToFile(std::const_pointer_cast(node), filename); + } + + bool NtxNodeXml::saveToFile(const NtxNodeCPtr& node, const std::string& filename) + { + if (!node) + { + std::cerr << "Cannot save null node to file: " << filename << std::endl; + return false; + } + + pugi::xml_document doc; + + // XML-Deklaration hinzufügen + pugi::xml_node declaration = doc.prepend_child(pugi::node_declaration); + declaration.append_attribute("version") = "1.0"; + declaration.append_attribute("encoding") = "UTF-8"; + + // Root-Node serialisieren + serializeNodeRecursive(node, doc); + + // In Datei speichern mit Formatierung + bool success = doc.save_file(filename.c_str(), " ", pugi::format_default, pugi::encoding_utf8); + + if (!success) + { + std::cerr << "Failed to save XML to file: " << filename << std::endl; + } + + return success; + } + + std::string NtxNodeXml::saveToString(const NtxNodePtr& node) + { + return saveToString(std::const_pointer_cast(node)); + } + + std::string NtxNodeXml::saveToString(const NtxNodeCPtr& node) + { + if (!node) + { + std::cerr << "Cannot serialize null node to string" << std::endl; + return ""; + } + + pugi::xml_document doc; + + // XML-Deklaration hinzufügen + pugi::xml_node declaration = doc.prepend_child(pugi::node_declaration); + declaration.append_attribute("version") = "1.0"; + declaration.append_attribute("encoding") = "UTF-8"; + + // Root-Node serialisieren + serializeNodeRecursive(node, doc); + + // In String konvertieren + std::ostringstream oss; + doc.save(oss, " ", pugi::format_default, pugi::encoding_utf8); + + return oss.str(); + } + +} // namespace ntx diff --git a/nodes/services/ntxnodexml.h b/nodes/services/ntxnodexml.h new file mode 100644 index 0000000..628ad1c --- /dev/null +++ b/nodes/services/ntxnodexml.h @@ -0,0 +1,27 @@ +#ifndef NTX_NODEXML_H +#define NTX_NODEXML_H + +#include +#include + +namespace ntx +{ + class NtxNodeXml + { + + public: + + static NtxNodePtr loadFromFile(const std::string& filename); + static NtxNodePtr loadFromString(const std::string& xmlContent); + + // Methoden zum Speichern + static bool saveToFile(const NtxNodePtr& node, const std::string& filename); + static bool saveToFile(const NtxNodeCPtr& node, const std::string& filename); + + static std::string saveToString(const NtxNodePtr& node); + static std::string saveToString(const NtxNodeCPtr& node); + + }; +} + +#endif // NTX_NODEXML_H \ No newline at end of file diff --git a/nodes/services/ntxundostack.cpp b/nodes/services/ntxundostack.cpp new file mode 100644 index 0000000..d666150 --- /dev/null +++ b/nodes/services/ntxundostack.cpp @@ -0,0 +1,156 @@ +#include + +namespace ntx +{ + + NtxUndoStack::NtxUndoStack() + : m_index(0) + , m_cleanIndex(0) + , m_undoLimit(0) + { + } + + void NtxUndoStack::push(NtxCommandUPtr command) + { + if (!command) + return; + + if (!command->canExecute()) + return; + + if (!command->execute()) + return; + + if (m_index < static_cast(m_commands.size())) + { + m_commands.erase(m_commands.begin() + m_index, m_commands.end()); + } + + m_commands.push_back(std::move(command)); + m_index++; + + if (m_cleanIndex > m_index) + m_cleanIndex = -1; + + checkUndoLimit(); + } + + bool NtxUndoStack::canUndo() const + { + return m_index > 0; + } + + bool NtxUndoStack::canRedo() const + { + return m_index < static_cast(m_commands.size()); + } + + void NtxUndoStack::undo() + { + if (!canUndo()) + return; + + m_index--; + + if (m_commands[m_index]->canUndo()) + { + m_commands[m_index]->undo(); + } + } + + void NtxUndoStack::redo() + { + if (!canRedo()) + return; + + if (m_commands[m_index]->canExecute()) + { + m_commands[m_index]->execute(); + } + + m_index++; + } + + int NtxUndoStack::index() const + { + return m_index; + } + + int NtxUndoStack::count() const + { + return static_cast(m_commands.size()); + } + + NtxString NtxUndoStack::undoText() const + { + if (!canUndo()) + return NtxString(); + + return m_commands[m_index - 1]->getDescription(); + } + + NtxString NtxUndoStack::redoText() const + { + if (!canRedo()) + return NtxString(); + + return m_commands[m_index]->getDescription(); + } + + void NtxUndoStack::clear() + { + m_commands.clear(); + m_index = 0; + m_cleanIndex = 0; + } + + void NtxUndoStack::setClean() + { + m_cleanIndex = m_index; + } + + bool NtxUndoStack::isClean() const + { + return m_cleanIndex == m_index; + } + + void NtxUndoStack::setUndoLimit(int limit) + { + m_undoLimit = limit; + checkUndoLimit(); + } + + int NtxUndoStack::undoLimit() const + { + return m_undoLimit; + } + + const NtxICommand* NtxUndoStack::command(int index) const + { + if (index < 0 || index >= static_cast(m_commands.size())) + return nullptr; + + return m_commands[index].get(); + } + + void NtxUndoStack::checkUndoLimit() + { + if (m_undoLimit <= 0) + return; + + int deleteCount = m_index - m_undoLimit; + if (deleteCount <= 0) + return; + + m_commands.erase(m_commands.begin(), m_commands.begin() + deleteCount); + m_index -= deleteCount; + + if (m_cleanIndex != -1) + { + m_cleanIndex -= deleteCount; + if (m_cleanIndex < 0) + m_cleanIndex = -1; + } + } + +} // namespace ntx \ No newline at end of file diff --git a/nodes/services/ntxundostack.h b/nodes/services/ntxundostack.h new file mode 100644 index 0000000..c7ba782 --- /dev/null +++ b/nodes/services/ntxundostack.h @@ -0,0 +1,56 @@ +#ifndef NTX_UNDO_STACK_H +#define NTX_UNDO_STACK_H + +#include +#include +#include + +namespace ntx +{ + + class NtxUndoStack + { + public: + + NtxUndoStack(); + ~NtxUndoStack() = default; + + NtxUndoStack(const NtxUndoStack&) = delete; + NtxUndoStack& operator=(const NtxUndoStack&) = delete; + + void push(NtxCommandUPtr command); + + bool canUndo() const; + bool canRedo() const; + + void undo(); + void redo(); + + int index() const; + int count() const; + + NtxString undoText() const; + NtxString redoText() const; + + void clear(); + void setClean(); + bool isClean() const; + + void setUndoLimit(int limit); + int undoLimit() const; + + const NtxICommand* command(int index) const; + + private: + + std::vector m_commands; + int m_index; + int m_cleanIndex; + int m_undoLimit; + + void checkUndoLimit(); + }; + +} // namespace ntx + +#endif // NTX_UNDO_STACK_H \ No newline at end of file diff --git a/nodes/util/ntxmapindex.cpp b/nodes/util/ntxmapindex.cpp new file mode 100644 index 0000000..fb9389f --- /dev/null +++ b/nodes/util/ntxmapindex.cpp @@ -0,0 +1,88 @@ +#include +#include // Zwingend erforderlich für std::views und std::ranges::to + +namespace ntx +{ + + // --- Lookup --- + + std::optional NtxMapIndex::indexOf(const std::string& key) const noexcept + { + auto it = m_keyToIndex.find(key); + if (it != m_keyToIndex.end()) + return it->second; + return std::nullopt; + } + + bool NtxMapIndex::containsKey(const std::string& key) const noexcept + { + return m_keyToIndex.contains(key); + } + + // --- Modification --- + + void NtxMapIndex::addKey(const std::string& key, size_t index) + { + m_keyToIndex[key] = index; + } + + void NtxMapIndex::removeAt(size_t index) + { + auto it = m_keyToIndex.begin(); + while (it != m_keyToIndex.end()) + { + if (it->second == index) + it = m_keyToIndex.erase(it); + else + { + if (it->second > index) + it->second--; + ++it; + } + } + } + + bool NtxMapIndex::removeKey(const std::string& key) + { + return m_keyToIndex.erase(key) > 0; + } + + bool NtxMapIndex::replaceKey(const std::string& oldKey, const std::string& newKey) + { + auto it = m_keyToIndex.find(oldKey); + if (it == m_keyToIndex.end() || oldKey == newKey) + return false; + if (m_keyToIndex.contains(newKey)) + return false; + + size_t idx = it->second; + m_keyToIndex.erase(it); + m_keyToIndex[newKey] = idx; + return true; + } + + bool NtxMapIndex::addAlias(const std::string& existingKey, const std::string& alias) + { + auto idx = indexOf(existingKey); + if (!idx || containsKey(alias)) + return false; + m_keyToIndex[alias] = *idx; + return true; + } + + size_t NtxMapIndex::size() const noexcept + { + return m_keyToIndex.size(); + } + + bool NtxMapIndex::empty() const noexcept + { + return m_keyToIndex.empty(); + } + + void NtxMapIndex::clear() noexcept + { + m_keyToIndex.clear(); + } + +} // namespace ntx \ No newline at end of file diff --git a/nodes/util/ntxmapindex.h b/nodes/util/ntxmapindex.h new file mode 100644 index 0000000..cd1895b --- /dev/null +++ b/nodes/util/ntxmapindex.h @@ -0,0 +1,61 @@ +#ifndef NTX_MAPINDEX_H +#define NTX_MAPINDEX_H + +#include +#include +#include +#include +#include + +namespace ntx +{ + + /** + * @brief Bidirektionaler Index: String-Key → size_t Position. + * + * Verwendet Komposition statt Vererbung. + * Key → Index: O(1) via unordered_map. + */ + class NtxMapIndex + { + + public: + + NtxMapIndex() = default; + + // --- Lookup --- + std::optional indexOf(const std::string& key) const noexcept; + bool containsKey(const std::string& key) const noexcept; + + // --- Iteration --- + + /// Iteriert über alle Key→Index Paare. + /// Template muss im Header bleiben. + template + void forEachKey(Fn&& fn) const + { + for (const auto& [key, idx] : m_keyToIndex) + fn(key, idx); + } + + // --- Modification --- + void addKey(const std::string& key, size_t index); + void removeAt(size_t index); + bool removeKey(const std::string& key); + bool replaceKey(const std::string& oldKey, const std::string& newKey); + bool addAlias(const std::string& existingKey, const std::string& alias); + + size_t size() const noexcept; + bool empty() const noexcept; + void clear() noexcept; + + private: + + std::unordered_map m_keyToIndex; + }; + + using NtxMapIndexPtr = std::shared_ptr; + +} // namespace ntx + +#endif // NTX_MAPINDEX_H \ No newline at end of file diff --git a/nodes/util/ntxmaptor.h b/nodes/util/ntxmaptor.h new file mode 100644 index 0000000..d49692e --- /dev/null +++ b/nodes/util/ntxmaptor.h @@ -0,0 +1,177 @@ +#ifndef NTX_MAPTOR_H +#define NTX_MAPTOR_H + +#include "ntxmapindex.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace ntx +{ + + /** + * @brief Generischer Maptor (Vector + Map), adaptiert an das NtxIPayload-Interface. + * + * Entfernt die STL-Container-Schnittstelle (push_back, begin, end, etc.) zugunsten + * der expliziten Payload-Methodik (setProperty, getProperty, etc.). + */ + template + class NtxMaptor + { + public: + + using NtxMapIndexPtr = std::shared_ptr; + + // Zugriff auf Iteratoren für Range-Based Loops (via valuesBegin/End) + using iterator = typename std::vector::iterator; + using const_iterator = typename std::vector::const_iterator; + + // --- Konstruktoren --- + + NtxMaptor() + : m_index(std::make_shared()) + { + } + + explicit NtxMaptor(const NtxMapIndexPtr& index) + { + if (!index) + throw std::invalid_argument("NtxMaptor: index must not be null"); + m_index = index; + } + + NtxMaptor(const NtxMaptor&) = default; + NtxMaptor& operator=(const NtxMaptor&) = default; + NtxMaptor(NtxMaptor&&) noexcept = default; + NtxMaptor& operator=(NtxMaptor&&) noexcept = default; + + virtual ~NtxMaptor() = default; + + // ========================================================================= + // NtxIPayload Interface-Adaption + // ========================================================================= + + // --- Kapazität --- + + [[nodiscard]] size_t getPropertyCount() const noexcept + { + return m_data.size(); + } + + void clearProperties() + { + m_data.clear(); + m_index->clear(); + } + + // --- Key-basierter Zugriff --- + + bool hasProperty(const std::string& key) const + { + return m_index->containsKey(key); + } + + /** + * @brief Setzt oder fügt einen Wert hinzu (Upsert). + * Wenn der Key existiert, wird der Wert überschrieben. + * Wenn nicht, wird er angehängt. + */ + void setProperty(const std::string& key, const T& value) + { + auto idx = m_index->indexOf(key); + if (idx) + { + m_data[*idx] = value; + } + else + { + m_index->addKey(key, m_data.size()); + m_data.push_back(value); + } + } + + // "Deducing this" für const/non-const getProperty(key) + template + auto&& getProperty(this Self&& self, const std::string& key) + { + auto idx = self.m_index->indexOf(key); + if (!idx) + throw std::out_of_range("NtxMaptor: Key not found: " + key); + return std::forward(self).m_data[*idx]; + } + + void removeProperty(const std::string& key) + { + auto idx = m_index->indexOf(key); + if (!idx) + return; + + size_t index = *idx; + // Index entfernen und nachfolgende Indizes im Index-Objekt korrigieren + m_index->removeAt(index); + // Daten aus Vektor entfernen + m_data.erase(m_data.begin() + static_cast(index)); + } + + // --- Index-basierter Zugriff --- + + bool hasProperty(size_t index) const + { + return index < m_data.size(); + } + + void setProperty(size_t index, const T& value) + { + if (index >= m_data.size()) + { + throw std::out_of_range("NtxMaptor: Index out of range"); + } + m_data[index] = value; + } + + // "Deducing this" für const/non-const getProperty(index) + template + auto&& getProperty(this Self&& self, size_t index) + { + return std::forward(self).m_data.at(index); + } + + // --- Iteratoren (NtxIPayload Style) --- + + // Erlaubt das Iterieren über die internen Werte + template + auto begin(this Self&& self) { return std::forward(self).m_data.begin(); } + + template + auto end(this Self&& self) { return std::forward(self).m_data.end(); } + + + // ========================================================================= + // Erweiterte Index-Funktionen (Alias, Replace) + // Diese bleiben erhalten, da sie spezifische NtxMapIndex Features sind + // ========================================================================= + + bool replaceKey(const std::string& oldKey, const std::string& newKey) + { + return m_index->replaceKey(oldKey, newKey); + } + + bool addAlias(const std::string& key, const std::string& alias) + { + return m_index->addAlias(key, alias); + } + + private: + + std::vector m_data; + NtxMapIndexPtr m_index; + }; + +} // namespace ntx + +#endif // NTX_MAPTOR_H \ No newline at end of file diff --git a/nodes/xml/ntc_dummy.xml b/nodes/xml/ntc_dummy.xml new file mode 100644 index 0000000..faa047b --- /dev/null +++ b/nodes/xml/ntc_dummy.xml @@ -0,0 +1,394 @@ + + + + + + 4096 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + On + + + On + On + Off + Off + Off + On + + + Default + Default + Default + Default + Default + Default + Default + Default + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + 22 + + + + + 2 + 29 + + + + + 3 + 26 + + + + + 4 + 27 + + + + + 5 + 28 + + + + + 6 + 21 + + + + + 7 + 30 + + + + + 8 + 31 + + + + + + + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14 + 15 + 16 + 17 + 18 + 19 + 20 + 23 + 24 + 25 + 32 + 33 + 34 + 35 + 36 + 37 + 38 + 41 + 42 + 43 + 44 + 45 + 46 + 47 + 48 + 49 + 50 + 51 + 52 + 53 + 54 + 55 + 56 + 57 + 58 + 59 + 60 + 61 + 62 + 63 + 64 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/nodes/xml/ntc_person.xml b/nodes/xml/ntc_person.xml new file mode 100644 index 0000000..5497497 --- /dev/null +++ b/nodes/xml/ntc_person.xml @@ -0,0 +1,140 @@ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + +
+ + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file