diff --git a/CMakeLists.txt b/CMakeLists.txt
index ea6ebcdb9462f50c8f8027a4f452bdcf298ed25a..794f40cd468bb1c4b6e7726239277c661ba74dd1 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -65,7 +65,7 @@ ENDIF()
 
 
 # Moderate version of the compiler flags
-set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -fPIC -Wextra -Wuninitialized")
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -fPIC -Wextra -Wuninitialized ")
 
 # use -DCMAKE_BUILD_TYPE=Debug in your cmake command to turn on the coverage option
 set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -O0 --coverage -fno-inline -fno-inline-small-functions -fno-default-inline")
diff --git a/example/demoApp.cc b/example/demoApp.cc
index 720256de01aad88ad7e4306cee4bc6b910638439..ccb28d92a3c3492dd73ccf862f56d38d587af92f 100644
--- a/example/demoApp.cc
+++ b/example/demoApp.cc
@@ -101,6 +101,8 @@ void ExampleApp::defineConnections() {
     dev("probeSignal", typeid(int), tableLength) [ macropulseNr ] >> cs("probeSignal");
 
     dumpConnections();
-
+    dumpConnectionGraph();
+    dumpGraph();
+    dumpModuleGraph("module-graph.dot");
 }
 
diff --git a/include/Application.h b/include/Application.h
index f5375e3d2fb51d5f1e76dca3d742501ab2b2a4b4..222dd511021402cbf904ee661cbe3ba132663bd6 100644
--- a/include/Application.h
+++ b/include/Application.h
@@ -67,6 +67,10 @@ namespace ChimeraTK {
        *  makeConnections() has been called. */
       void dumpConnections();
 
+      /** Create Graphviz dot graph and write to file. The graph will contain the connections made in the initilise()
+       * function. @see dumpConnections */
+      void dumpConnectionGraph(const std::string &filename = {"connections-graph.dot"});
+
       /** Enable warning about unconnected variables. This can be helpful to identify missing connections but is
        *  disabled by default since it may often be very noisy. */
       void warnUnconnectedVariables() { enableUnconnectedVariablesWarning = true; }
@@ -154,6 +158,8 @@ namespace ChimeraTK {
       friend class Module;
       friend class VariableNetwork;
       friend class VariableNetworkNode;
+      friend class VariableNetworkGraphDumpingVisitor;
+      friend class XMLGeneratorVisitor;
 
       template<typename UserType>
       friend class Accessor;
diff --git a/include/ApplicationException.h b/include/ApplicationException.h
index 8db387e31dfaa223eebc5bed31b4cc26a4dd4a32..07a7d63b93b15aee5e3d673539d69fdcf138ae5e 100644
--- a/include/ApplicationException.h
+++ b/include/ApplicationException.h
@@ -9,6 +9,7 @@
 #define CHIMERATK_APPLICATION_EXCEPTION_H
 
 #include <exception>
+#include <string>
 
 namespace ChimeraTK {
 
diff --git a/include/EntityOwner.h b/include/EntityOwner.h
index ad6f976dd8ceca189e3092a75b2f11bd78192e89..5c59353f47d8b7e4d8e23588e2dbd2ea340b6572 100644
--- a/include/EntityOwner.h
+++ b/include/EntityOwner.h
@@ -126,6 +126,8 @@ namespace ChimeraTK {
        *  through all sub-modules and add all found variables directly to the VirtualModule. */
       VirtualModule flatten();
 
+      void accept(Visitor<EntityOwner>& visitor) const { visitor.dispatch(*this); }
+
       /** Print the full hierarchy to stdout. */
       void dump(const std::string &prefix="") const;
 
@@ -154,12 +156,6 @@ namespace ChimeraTK {
       void findTagAndAppendToModule(VirtualModule &module, const std::string &tag, bool eliminateAllHierarchies=false,
                                     bool eliminateFirstHierarchy=false, bool negate=false) const;
 
-      /** Create Graphviz dot graph write to stream, excluding the surrounding digraph command */
-      void dumpGraphInternal(std::ostream &stream, bool showVariables) const;
-
-      /** Clean a fully qualified entity name so it can be used as a dot node name (i.e. strip slashes etc.) */
-      std::string cleanDotNode(std::string fullName) const;
-
       /** The name of this instance */
       std::string _name;
 
diff --git a/include/Module.h b/include/Module.h
index f0681c50aefdd2fb5a762e534f59a0761a006706..8dccad87cad1b029ee8e739fec8c73a6fec29e8b 100644
--- a/include/Module.h
+++ b/include/Module.h
@@ -108,6 +108,12 @@ namespace ChimeraTK {
 
       EntityOwner* getOwner() const { return _owner; }
 
+      /**
+       * Explcitly add accept() method so that we can distinguish between a Module and an EntityOwner in the Visitor.
+
+       */
+      void accept(Visitor<Module>& visitor) const { visitor.dispatch(*this); }
+
     protected:
 
       /** Owner of this instance */
diff --git a/include/ModuleGraphVisitor.h b/include/ModuleGraphVisitor.h
new file mode 100644
index 0000000000000000000000000000000000000000..eec32431f305f00e9af68747cf571ac54ed77758
--- /dev/null
+++ b/include/ModuleGraphVisitor.h
@@ -0,0 +1,33 @@
+#pragma once
+
+#include <string>
+#include <ostream>
+
+#include "Visitor.h"
+
+namespace ChimeraTK {
+// Forward declarations
+class VariableNetworkNode;
+class EntityOwner;
+class Module;
+
+/**
+ * @brief The ModuleGraphVisitor class
+ *
+ * This class is responsible for generating the Graphiviz representation of the module hierarchy.
+ */
+class ModuleGraphVisitor : public Visitor<EntityOwner, Module, VariableNetworkNode> {
+public:
+    ModuleGraphVisitor(std::ostream& stream, bool showVariables = true);
+    virtual ~ModuleGraphVisitor() {}
+
+    void dispatch(const EntityOwner &owner);
+    void dispatch(const Module &module);
+    void dispatch(const VariableNetworkNode &node);
+private:
+    std::ostream& _stream;
+    bool _showVariables;
+
+    void dumpEntityOwner(const EntityOwner &owner);
+};
+} // namespace ChimeraTK
diff --git a/include/VariableNetwork.h b/include/VariableNetwork.h
index 834e5b64c65c3b755893f24e05f6a1a387234de7..c25b6389fd7c58bd2cff6043266de9c59ee592f4 100644
--- a/include/VariableNetwork.h
+++ b/include/VariableNetwork.h
@@ -18,6 +18,7 @@
 
 #include "Flags.h"
 #include "VariableNetworkNode.h"
+#include "Visitor.h"
 
 namespace ChimeraTK {
 
@@ -77,6 +78,8 @@ namespace ChimeraTK {
       /** Dump the network structure to std::cout. The optional linePrefix will be prepended to all lines. */
       void dump(const std::string& linePrefix="", std::ostream& stream=std::cout) const;
 
+      void accept(Visitor<VariableNetwork> &visitor) const;
+
       /** Compare two networks */
       bool operator==(const VariableNetwork &other) const {
         if(other.valueType != valueType) return false;
@@ -109,7 +112,7 @@ namespace ChimeraTK {
       //void addTrigger(VariableNetworkNode trigger);
 
       /** Check if the network is legally configured */
-      void check();
+      void check() const;
 
       /** Check the flag if the network connections has been created already */
       bool isCreated() const { return flagIsCreated; }
diff --git a/include/VariableNetworkDumpingVisitor.h b/include/VariableNetworkDumpingVisitor.h
new file mode 100644
index 0000000000000000000000000000000000000000..60282f2467e644a1560be8c340dca7b2cf2126ec
--- /dev/null
+++ b/include/VariableNetworkDumpingVisitor.h
@@ -0,0 +1,29 @@
+#pragma once
+
+#include "Visitor.h"
+#include "VariableNetworkNodeDumpingVisitor.h"
+
+#include <string>
+
+namespace ChimeraTK {
+
+// Forward declarations
+class VariableNetwork;
+
+/**
+ * @brief The VariableNetworkDumpingVisitor class
+ *
+ * This class provides a textual dump of the VariableNetwork
+ */
+class VariableNetworkDumpingVisitor : public Visitor<VariableNetwork>, public VariableNetworkNodeDumpingVisitor {
+public:
+    VariableNetworkDumpingVisitor(const std::string &prefix, std::ostream &stream);
+    virtual ~VariableNetworkDumpingVisitor() {}
+    void dispatch(const VariableNetwork& t);
+    using Visitor<VariableNetworkNode>::dispatch;
+
+private:
+    std::string _prefix;
+};
+
+} // namespace ChimeraTK
diff --git a/include/VariableNetworkGraphDumpingVisitor.h b/include/VariableNetworkGraphDumpingVisitor.h
new file mode 100644
index 0000000000000000000000000000000000000000..784fd68c077db4aed406fcf26caa3cc525bf8662
--- /dev/null
+++ b/include/VariableNetworkGraphDumpingVisitor.h
@@ -0,0 +1,41 @@
+#pragma once
+
+#include "Visitor.h"
+#include "VariableNetworkNodeDumpingVisitor.h"
+
+#include <map>
+
+namespace ChimeraTK {
+
+// Forward Declarations
+
+class Application;
+class VariableNetwork;
+
+/**
+ * @brief The VariableNetworkGraphDumpingVisitor class
+ *
+ * This class provides a Graphiviz dump of the VariableNetwork.
+ * Due to the potential size of the resulting graph, it is recommended to use SVG for
+ * rendering the resulting graph.
+ */
+class VariableNetworkGraphDumpingVisitor : public Visitor<Application, VariableNetwork>, VariableNetworkNodeDumpingVisitor {
+public:
+    VariableNetworkGraphDumpingVisitor(std::ostream& stream);
+    virtual ~VariableNetworkGraphDumpingVisitor() {}
+    void dispatch(const Application& t);
+    void dispatch(const VariableNetwork& t);
+    void dispatch(const VariableNetworkNode& t);
+private:
+    std::map<std::string, std::string> _triggerMap;
+    std::list<std::string> _triggerConnections;
+    std::list<std::string> _prefix;
+    unsigned _networkCount;
+    unsigned _triggerCount;
+
+    std::string prefix() { return _prefix.back(); }
+    void pushPrefix(const std::string& prefix) { _prefix.push_back(prefix); }
+    void popPrefix() { _prefix.pop_back(); }
+};
+
+} // namespace ChimeraTK
diff --git a/include/VariableNetworkNode.h b/include/VariableNetworkNode.h
index 7ee500c77a8faa352c2a277d18ac0adabbe6e080..5be94a3a071249994458a4bf4755028e318b8c1d 100644
--- a/include/VariableNetworkNode.h
+++ b/include/VariableNetworkNode.h
@@ -19,10 +19,7 @@
 
 #include "Flags.h"
 #include "ConstantAccessor.h"
-
-namespace xmlpp {
-  class Element;
-}
+#include "Visitor.h"
 
 namespace ChimeraTK {
 
@@ -112,12 +109,6 @@ namespace ChimeraTK {
       /** Print node information to std::cout */
       void dump(std::ostream& stream=std::cout) const;
 
-      /** Create an XML node describing this network node as seen by the control syste. If the type is not
-       *  NodeType::ControlSystem, this function does nothing. Otherwise the correct directory hierarchy will be
-       *  created (if not yet existing) and a variable tag will be created containing the externally visible
-       *  properties of this variable. */
-      void createXML(xmlpp::Element *rootElement) const;
-
       /** Check if the node already has an owner */
       bool hasOwner() const;
 
@@ -161,6 +152,8 @@ namespace ChimeraTK {
 
       void setOwningModule(EntityOwner *newOwner) const;
 
+      void accept(Visitor<VariableNetworkNode> &visitor) const;
+
     //protected:  @todo make protected again (with proper interface extension)
 
       boost::shared_ptr<VariableNetworkNode_data> pdata;
diff --git a/include/VariableNetworkNodeDumpingVisitor.h b/include/VariableNetworkNodeDumpingVisitor.h
new file mode 100644
index 0000000000000000000000000000000000000000..fbf391ee62f983b65e5035661847a4ab3bc3512f
--- /dev/null
+++ b/include/VariableNetworkNodeDumpingVisitor.h
@@ -0,0 +1,75 @@
+#pragma once
+
+#include "Visitor.h"
+
+#include <list>
+#include <ostream>
+#include <functional> // for std::reference_wrapper
+
+namespace ChimeraTK {
+
+// Forward declarations
+class VariableNetworkNode;
+
+/**
+ * @brief A helper class to replace the output stream temporarily
+ *
+ * This is a helper class that is used in the Graphviz dumper to be able to dump the nodes to a stringstream
+ * instead of directly to the file.
+ *
+ * Ideally, the pushStream()/popStream() functions should be called in pairs but popStream() will do nothing
+ * if the stack is empty.
+ */
+class PushableStream {
+public:
+    PushableStream(std::ostream &stream) : _streamStack{stream} {}
+    virtual ~PushableStream() {}
+
+    void pushStream(std::ostream& stream) {
+        _streamStack.push_back(stream);
+    }
+
+    std::ostream& stream() { return _streamStack.back().get(); }
+
+    void popStream() {
+        if (_streamStack.size() == 1)
+            return;
+
+        _streamStack.pop_back();
+    }
+private:
+    std::list<std::reference_wrapper<std::ostream>> _streamStack;
+};
+
+/**
+ * @brief The VariableNetworkNodeDumpingVisitor class
+ *
+ * This class is serving as one of the base classes for the Graphviz dumper as well as the textual dumper
+ * providing detailed information about a node.
+ */
+class VariableNetworkNodeDumpingVisitor : public Visitor<VariableNetworkNode>, public PushableStream {
+public:
+    /**
+     * @brief VariableNetworkNodeDumpingVisitor::VariableNetworkNodeDumpingVisitor
+     * @param stream instance of std::ostream to write to
+     * @param separator the separator to use
+     *
+     * Separator is used to be able to use the function in the Graphviz and textual connection dumper.
+     * We are using newlines for Graphviz, and space for textual
+     */
+    VariableNetworkNodeDumpingVisitor(std::ostream &stream, const std::string& separator);
+    virtual ~VariableNetworkNodeDumpingVisitor() {}
+
+    /**
+     * @brief dispatch
+     * @param t Node to visit
+     *
+     * Visitor function for VariableNetworkNode. Will dump a verbose description of the node
+     */
+    void dispatch(const VariableNetworkNode& t);
+
+private:
+    std::string _separator;
+};
+
+} // namespace ChimeraTK
diff --git a/include/Visitor.h b/include/Visitor.h
new file mode 100644
index 0000000000000000000000000000000000000000..b15e78ca7e8615534199c15efa5ca440da6a038b
--- /dev/null
+++ b/include/Visitor.h
@@ -0,0 +1,23 @@
+#pragma once
+
+namespace ChimeraTK {
+
+/* Losely based on https://stackoverflow.com/questions/11796121/implementing-the-visitor-pattern-using-c-templates#11802080 */
+
+template <typename... Types>
+class Visitor;
+
+template <typename T>
+class Visitor<T> {
+public:
+    virtual void dispatch(const T& t) = 0;
+};
+
+template <typename T, typename... Types>
+class Visitor<T, Types...> : public Visitor<T>, public Visitor<Types...> {
+public:
+    using Visitor<Types...>::dispatch;
+    using Visitor<T>::dispatch;
+};
+
+} // namespace ChimeraTK
diff --git a/include/VisitorHelper.h b/include/VisitorHelper.h
new file mode 100644
index 0000000000000000000000000000000000000000..cce1390e3d63ba94f470c8e07c75fb3c43352a32
--- /dev/null
+++ b/include/VisitorHelper.h
@@ -0,0 +1,14 @@
+#pragma once
+
+#include <string>
+
+namespace ChimeraTK {
+class VariableNetworkNode;
+
+namespace detail {
+
+std::string encodeDotNodeName(std::string name);
+std::string nodeName(const VariableNetworkNode& node);
+
+} // namespace detail)
+}// namespace ChimeraTK
diff --git a/include/XMLGeneratorVisitor.h b/include/XMLGeneratorVisitor.h
new file mode 100644
index 0000000000000000000000000000000000000000..63335cad61bc95291507263d77c069c194f8e6b5
--- /dev/null
+++ b/include/XMLGeneratorVisitor.h
@@ -0,0 +1,37 @@
+#pragma once
+
+#include "Visitor.h"
+
+#include <string>
+#include <memory>
+
+// Forward declarations
+namespace xmlpp {
+    class Document;
+    class Element;
+}
+
+namespace ChimeraTK {
+// Forward declarations
+class Application;
+class VariableNetworkNode;
+
+/**
+ * @brief The XMLGeneratorVisitor class
+ *
+ * This class is responsible for generating the XML representation of the Variables in an Application
+ */
+class XMLGeneratorVisitor : public Visitor<Application, VariableNetworkNode> {
+public:
+    XMLGeneratorVisitor();
+    virtual ~XMLGeneratorVisitor() {}
+    void dispatch(const Application& app);
+    void dispatch(const VariableNetworkNode &node);
+
+    void save(const std::string &filename);
+private:
+    std::shared_ptr<xmlpp::Document> _doc;
+    xmlpp::Element *_rootElement;
+};
+
+} // namespace ChimeraTK
diff --git a/src/Application.cc b/src/Application.cc
index 5050b41ea63adaa9377604d9cd8c3183154703cf..7b7c41968d126a60769a614a3a9e80feb1d065a3 100644
--- a/src/Application.cc
+++ b/src/Application.cc
@@ -11,8 +11,6 @@
 
 #include <boost/fusion/container/map.hpp>
 
-#include <libxml++/libxml++.h>
-
 #include <mtca4u/BackendFactory.h>
 
 #include "Application.h"
@@ -27,6 +25,9 @@
 #include "ConstantAccessor.h"
 #include "TestDecoratorRegisterAccessor.h"
 #include "DebugDecoratorRegisterAccessor.h"
+#include "Visitor.h"
+#include "VariableNetworkGraphDumpingVisitor.h"
+#include "XMLGeneratorVisitor.h"
 
 using namespace ChimeraTK;
 
@@ -206,27 +207,9 @@ void Application::generateXML() {
   // also search for unconnected nodes - this is here only executed to print the warnings
   processUnconnectedNodes();
 
-  // create XML document with root node
-  xmlpp::Document doc;
-  xmlpp::Element *rootElement = doc.create_root_node("application", "https://github.com/ChimeraTK/ApplicationCore");
-  rootElement->set_attribute("name",applicationName);
-
-  for(auto &network : networkList) {
-
-    // perform checks
-    network.check();
-
-    // create xml code for the feeder (if it is a control system node)
-    auto feeder = network.getFeedingNode();
-    feeder.createXML(rootElement);
-
-    // create xml code for the consumers
-    for(auto &consumer : network.getConsumingNodes()) {
-      consumer.createXML(rootElement);
-    }
-
-  }
-  doc.write_to_file_formatted(applicationName+".xml");
+  XMLGeneratorVisitor visitor;
+  visitor.dispatch(*this);
+  visitor.save(applicationName + ".xml");
 }
 
 /*********************************************************************************************************************/
@@ -536,6 +519,13 @@ void Application::dumpConnections() {
   std::cout << "=====================================================================" << std::endl;  // LCOV_EXCL_LINE
 }                                                                                                     // LCOV_EXCL_LINE
 
+void Application::dumpConnectionGraph(const std::string& fileName) {
+    std::fstream file{fileName, std::ios_base::out};
+
+    VariableNetworkGraphDumpingVisitor visitor{file};
+    visitor.dispatch(*this);
+}
+
 /*********************************************************************************************************************/
 
 Application::TypedMakeConnectionCaller::TypedMakeConnectionCaller(Application &owner, VariableNetwork &network)
diff --git a/src/EntityOwner.cc b/src/EntityOwner.cc
index e29f0ea01432d1913672f1af1cc83098e8122804..19d4ffe98b749ba9608407da7b2880a8b97deb8d 100644
--- a/src/EntityOwner.cc
+++ b/src/EntityOwner.cc
@@ -11,6 +11,7 @@
 
 #include "EntityOwner.h"
 #include "Module.h"
+#include "ModuleGraphVisitor.h"
 #include "VirtualModule.h"
 
 namespace ChimeraTK {
@@ -204,75 +205,16 @@ namespace ChimeraTK {
 
   void EntityOwner::dumpGraph(const std::string& fileName) const {
     std::fstream file(fileName, std::ios_base::out);
-    file << "digraph G {" << std::endl;
-    dumpGraphInternal(file, true);
-    file << "}" << std::endl;
-    file.close();
+    ModuleGraphVisitor v{file, true};
+    v.dispatch(*this);
   }
 
 /*********************************************************************************************************************/
 
   void EntityOwner::dumpModuleGraph(const std::string& fileName) const {
     std::fstream file(fileName, std::ios_base::out);
-    file << "digraph G {" << std::endl;
-    dumpGraphInternal(file, false);
-    file << "}" << std::endl;
-    file.close();
-  }
-
-/*********************************************************************************************************************/
-
-  std::string EntityOwner::cleanDotNode(std::string fullName) const {
-    std::replace(fullName.begin(), fullName.end(), '/', '_');
-    std::replace(fullName.begin(), fullName.end(), ':', '_');
-    return fullName;
-  }
-
-/*********************************************************************************************************************/
-
-  void EntityOwner::dumpGraphInternal(std::ostream &stream, bool showVariables) const {
-
-    std::string myDotNode = cleanDotNode(getQualifiedName());
-
-    stream << "  " << myDotNode << "[label=\"" << getName() << "\"";
-    if(_eliminateHierarchy) {
-      stream << ",style=dotted";
-    }
-    if(getModuleType() == ModuleType::ModuleGroup) {
-      stream << ",peripheries=2";
-    }
-    if(getModuleType() == ModuleType::ApplicationModule) {
-      stream << ",penwidth=3";
-    }
-    stream << "]" << std::endl;
-
-    if(showVariables) {
-      for(auto &node : getAccessorList()) {
-        std::string dotNode = cleanDotNode(node.getQualifiedName());
-        stream << "  " <<  dotNode << "[label=\"{" << node.getName() << "| {";
-        bool first = true;
-        for(auto tag : node.getTags()) {
-          if(!first) {
-            stream << "|";
-          }
-          else {
-            first = false;
-          }
-          stream << tag;
-        }
-        stream << "}}\", shape=record]" << std::endl;
-        stream << "  " << myDotNode << " -> " << dotNode  << std::endl;
-      }
-    }
-
-    for(auto submodule : getSubmoduleList()) {
-      if(submodule->getModuleType() == ModuleType::Device ||
-         submodule->getModuleType() == ModuleType::ControlSystem) continue;
-      std::string dotNode = cleanDotNode(submodule->getQualifiedName());
-      stream << "  " << myDotNode << " -> " << dotNode << std::endl;
-      submodule->dumpGraphInternal(stream, showVariables);
-    }
-
+    ModuleGraphVisitor v{file, false};
+    v.dispatch(*this);
   }
 
 /*********************************************************************************************************************/
diff --git a/src/ModuleGraphVisitor.cc b/src/ModuleGraphVisitor.cc
new file mode 100644
index 0000000000000000000000000000000000000000..9d1e8a6d54aa5f3dc06bdb2d0dba7f15a9de8f16
--- /dev/null
+++ b/src/ModuleGraphVisitor.cc
@@ -0,0 +1,73 @@
+#include "ModuleGraphVisitor.h"
+
+#include "EntityOwner.h"
+#include "Module.h"
+#include "VariableNetworkNode.h"
+#include "VisitorHelper.h"
+
+namespace ChimeraTK {
+
+ModuleGraphVisitor::ModuleGraphVisitor(std::ostream& stream, bool showVariables)
+    : Visitor<ChimeraTK::EntityOwner, ChimeraTK::Module, ChimeraTK::VariableNetworkNode> ()
+    , _stream(stream)
+    , _showVariables(showVariables) {}
+
+void ModuleGraphVisitor::dispatch(const EntityOwner &owner) {
+    /* If we start with an entity owner, consider ourselves the start of a graph */
+    /* When descending from here, we only use Module directly */
+    _stream << "digraph G {" << "\n";
+    dumpEntityOwner(owner);
+    _stream << "}" << std::endl;
+}
+
+void ModuleGraphVisitor::dispatch(const VariableNetworkNode &node) {
+    std::string dotNode = detail::encodeDotNodeName(node.getQualifiedName());
+    _stream << "  " <<  dotNode << "[label=\"{" << node.getName() << "| {";
+    bool first = true;
+    for(auto tag : node.getTags()) {
+      if(!first) {
+        _stream << "|";
+      }
+      else {
+        first = false;
+      }
+      _stream << tag;
+    }
+    _stream << "}}\", shape=record]" << std::endl;
+}
+
+void ModuleGraphVisitor::dispatch(const Module &module) {
+    dumpEntityOwner(static_cast<const EntityOwner &>(module));
+}
+
+void ModuleGraphVisitor::dumpEntityOwner(const EntityOwner &module) {
+    std::string myDotNode = detail::encodeDotNodeName(module.getQualifiedName());
+    _stream << "  " << myDotNode << "[label=\"" << module.getName() << "\"";
+    if(module.getEliminateHierarchy()) {
+      _stream << ",style=dotted";
+    }
+    if(module.getModuleType() == EntityOwner::ModuleType::ModuleGroup) {
+      _stream << ",peripheries=2";
+    }
+    if(module.getModuleType() == EntityOwner::ModuleType::ApplicationModule) {
+      _stream << ",penwidth=3";
+    }
+    _stream << "]" << std::endl;
+
+    if(_showVariables) {
+      for(auto &node : module.getAccessorList()) {
+          std::string dotNode = detail::encodeDotNodeName(detail::nodeName(node));
+          node.accept(*this);
+          _stream << "  " << myDotNode << " -> " << dotNode << std::endl;
+      }
+    }
+
+    for(const Module *submodule : module.getSubmoduleList()) {
+      if(submodule->getModuleType() == EntityOwner::ModuleType::Device ||
+         submodule->getModuleType() == EntityOwner::ModuleType::ControlSystem) continue;
+      std::string dotNode = detail::encodeDotNodeName(submodule->getQualifiedName());
+      _stream << "  " << myDotNode <<  " -> " << dotNode << std::endl;
+      submodule->accept(*this);
+    }
+}
+}
diff --git a/src/VariableNetwork.cc b/src/VariableNetwork.cc
index fb088796c73ea968ff4e8ee968b6c039d6351365..07125c9bf0408a3b5b3fbc94b1be9e32b57af9f1 100644
--- a/src/VariableNetwork.cc
+++ b/src/VariableNetwork.cc
@@ -7,10 +7,9 @@
 
 #include <sstream>
 
-#include <libxml++/libxml++.h>
-
 #include "VariableNetwork.h"
 #include "Application.h"
+#include "VariableNetworkDumpingVisitor.h"
 
 namespace ChimeraTK {
 
@@ -90,42 +89,12 @@ namespace ChimeraTK {
   /*********************************************************************************************************************/
 
   void VariableNetwork::dump(const std::string& linePrefix, std::ostream& stream) const {
-    stream << linePrefix << "VariableNetwork";
-    stream << " [ptr: " << this << "]";
-    stream << " {" << std::endl;
-    stream << linePrefix << "  value type = " << valueType->name() << ", engineering unit = " << engineeringUnit << std::endl;
-    stream << linePrefix << "  trigger type = ";
-    try {
-      TriggerType tt = getTriggerType(false);
-      if(tt == TriggerType::feeder) stream << "feeder" << std::endl;
-      if(tt == TriggerType::pollingConsumer) stream << "pollingConsumer" << std::endl;
-      if(tt == TriggerType::external) stream << "external" << std::endl;
-      if(tt == TriggerType::none) stream << "none" << std::endl;
-    }
-    catch(ApplicationExceptionWithID<ApplicationExceptionID::illegalVariableNetwork> &e) {
-      stream << "**error**" << std::endl;
-    }
-    stream << linePrefix << "  feeder";
-    if(hasFeedingNode()) {
-      getFeedingNode().dump(stream);
-    }
-    else {
-      stream << " **error, no feeder found**" << std::endl;
-    }
-    stream << linePrefix << "  consumers: " << countConsumingNodes() << std::endl;
-    size_t count = 0;
-    for(auto &consumer : nodeList) {
-      if(consumer.getDirection() != VariableDirection::consuming) continue;
-      stream << linePrefix << "    # " << ++count << ":";
-      consumer.dump(stream);
-    }
-    if(hasFeedingNode()) {
-      if(getFeedingNode().hasExternalTrigger()) {
-        stream << linePrefix << "  external trigger node: ";
-        getFeedingNode().getExternalTrigger().dump(stream);
-      }
-    }
-    stream << linePrefix << "}" << std::endl;
+      VariableNetworkDumpingVisitor visitor{linePrefix, stream};
+      accept(visitor);
+  }
+
+  void VariableNetwork::accept(Visitor<VariableNetwork> &visitor) const {
+      visitor.dispatch(*this);
   }
 
   /*********************************************************************************************************************/
@@ -177,7 +146,7 @@ namespace ChimeraTK {
 
   /*********************************************************************************************************************/
 
-  void VariableNetwork::check() {
+  void VariableNetwork::check() const {
     // must have consuming nodes
     if(countConsumingNodes() == 0) {
       std::stringstream msg;
diff --git a/src/VariableNetworkDumpingVisitor.cc b/src/VariableNetworkDumpingVisitor.cc
new file mode 100644
index 0000000000000000000000000000000000000000..ee5e114b9eeda2f123c887e9d379ca5bdd24cd45
--- /dev/null
+++ b/src/VariableNetworkDumpingVisitor.cc
@@ -0,0 +1,52 @@
+#include "ApplicationException.h"
+#include "VariableNetwork.h"
+#include "VariableNetworkDumpingVisitor.h"
+
+namespace ChimeraTK {
+
+VariableNetworkDumpingVisitor::VariableNetworkDumpingVisitor(const std::string& prefix, std::ostream &stream)
+    : Visitor<ChimeraTK::VariableNetwork>()
+    , VariableNetworkNodeDumpingVisitor(stream, " ")
+    , _prefix(prefix) {}
+
+void VariableNetworkDumpingVisitor::dispatch(const VariableNetwork& t) {
+    stream() << _prefix << "VariableNetwork";
+    stream() << " {" << std::endl;
+    stream() << _prefix << "  value type = " << boost::core::demangle(t.getValueType().name()) << ", engineering unit = " << t.getUnit() << std::endl;
+    stream() << _prefix << "  trigger type = ";
+    try {
+      auto tt = t.getTriggerType(false);
+      if(tt == VariableNetwork::TriggerType::feeder) stream() << "feeder";
+      if(tt == VariableNetwork::TriggerType::pollingConsumer) stream() << "pollingConsumer";
+      if(tt == VariableNetwork::TriggerType::external) stream() << "external";
+      if(tt == VariableNetwork::TriggerType::none) stream() << "none";
+      stream() << std::endl;
+    }
+    catch(ApplicationExceptionWithID<ApplicationExceptionID::illegalVariableNetwork> &e) {
+      stream() << "**error**" << std::endl;
+    }
+    stream() << _prefix << "  feeder";
+    if(t.hasFeedingNode()) {
+      t.getFeedingNode().accept(*this);
+    }
+    else {
+      stream() << " **error, no feeder found**" << std::endl;
+    }
+    stream() << _prefix << "  consumers: " << t.countConsumingNodes() << std::endl;
+    size_t count = 0;
+    for(auto &consumer : t.getConsumingNodes()) {
+      if(consumer.getDirection() != VariableDirection::consuming) continue;
+      stream() << _prefix << "    # " << ++count << ":";
+      consumer.accept(*this);
+    }
+    if(t.hasFeedingNode()) {
+      if(t.getFeedingNode().hasExternalTrigger()) {
+        stream() << _prefix << "  external trigger node: ";
+        t.getFeedingNode().getExternalTrigger().accept(*this);
+      }
+    }
+    stream() << _prefix << "}" << std::endl;
+}
+
+
+} // namespace ChimeraTK
diff --git a/src/VariableNetworkGraphDumpingVisitor.cc b/src/VariableNetworkGraphDumpingVisitor.cc
new file mode 100644
index 0000000000000000000000000000000000000000..4431bb55c1e50b6d4e78202fcd56f3eaf2200707
--- /dev/null
+++ b/src/VariableNetworkGraphDumpingVisitor.cc
@@ -0,0 +1,148 @@
+#include "Application.h"
+#include "VariableNetworkGraphDumpingVisitor.h"
+#include "VariableNetwork.h"
+#include "VisitorHelper.h"
+
+#include <algorithm>
+#include <typeinfo>
+#include <sstream>
+
+namespace ChimeraTK {
+
+void VariableNetworkGraphDumpingVisitor::dispatch(const VariableNetwork& network) {
+    std::string networkPrefix = "network_" + std::to_string(_networkCount++);
+    pushPrefix(networkPrefix);
+
+    stream() << "  subgraph cluster_" << prefix() << " {\n"
+         << "    fontsize=\"8\";\n"
+         << "    style=\"filled,rounded\";\n"
+         << "    color=black;\n"
+         << "    fillcolor=white;\n"
+         << "    ordering=out;\n"
+         << "    label=\"" << boost::core::demangle(typeid(network).name()) << "(" << &network << ")\\n"
+         <<              network.getDescription() << "\\n"
+         <<              "value type = " << boost::core::demangle(network.getValueType().name()) << "\\n"
+         <<              "engineering unit = " << network.getUnit() << "\";\n";
+
+    std::string feeder;
+    std::string trigger;
+    if (network.hasFeedingNode()) {
+        auto feederNode = network.getFeedingNode();
+        // We are inside a trigger network... Consumers will be skipped below. Make the feeder a "global" variable
+        if (!network.getConsumingNodes().empty() && network.getConsumingNodes().begin()->getType() == NodeType::TriggerReceiver) {
+            feeder = detail::encodeDotNodeName(detail::nodeName(feederNode));
+            if (_triggerMap.find(feeder) == _triggerMap.end()) {
+                std::stringstream ss;
+                pushStream(ss); pushPrefix("");
+                feederNode.accept(*this);
+                _triggerMap[feeder] = ss.str();
+                popPrefix(); popStream();
+            }
+        } else {
+            feeder = prefix() + detail::encodeDotNodeName(detail::nodeName(feederNode));
+            feederNode.accept(*this);
+        }
+
+        if (feederNode.hasExternalTrigger()) {
+            auto triggerNode = feederNode.getExternalTrigger();
+            trigger = detail::encodeDotNodeName(detail::nodeName(triggerNode));
+            if (_triggerMap.find(trigger) == _triggerMap.end()) {
+                std::stringstream ss;
+                pushStream(ss); pushPrefix("");
+                triggerNode.accept(*this);
+                _triggerMap[trigger] = ss.str();
+                popPrefix(); popStream();
+            }
+        }
+    }
+
+    for (auto &consumerNode : network.getConsumingNodes()) {
+        if (consumerNode.getDirection() != VariableDirection::consuming)
+            continue;
+
+        if (consumerNode.getType() == NodeType::TriggerReceiver)
+            continue;
+
+        auto consumer = prefix() + detail::encodeDotNodeName(detail::nodeName(consumerNode));
+        consumerNode.accept(*this);
+        std::string helperNode;
+
+        if (!trigger.empty()) {
+            // Create trigger connection diamond
+            _triggerCount++;
+            helperNode = prefix() + "_trigger_helper_" + std::to_string(_triggerCount);
+            stream() << helperNode << "[label=\"\",shape=diamond,style=\"filled\",color=black,width=.3,height=.3,fixedsize=true,fillcolor=\"#ffcc00\"]\n";
+        }
+
+        stream() << feeder << " -> ";
+        if (!helperNode.empty()) {
+            stream() << helperNode << " -> " << consumer << "\n";
+            // Hack: Make trigger lower in rank than all entry points to subgraphs
+            _triggerConnections.push_back(trigger + " -> " + feeder + "[style = invis]\n");
+
+            _triggerConnections.push_back(trigger + " -> " + helperNode + "[style = dashed, color=grey,tailport=s]\n");
+        } else {
+            stream() << consumer << "\n";
+        }
+    }
+
+    stream() << "}\n";
+    popPrefix();
+}
+
+VariableNetworkGraphDumpingVisitor::VariableNetworkGraphDumpingVisitor(std::ostream& stream)
+    : Visitor<Application, VariableNetwork> ()
+    , VariableNetworkNodeDumpingVisitor(stream, "\\n")
+    , _triggerMap()
+    , _triggerConnections()
+    , _networkCount(0)
+    , _triggerCount(0) {}
+
+
+void VariableNetworkGraphDumpingVisitor::dispatch(const Application& t) {
+    stream() << "digraph application {\n"
+         //<< "  rankdir = LR;\n"
+         << "  fontname=\"Sans\";\n"
+         << "  fontsize=\"10\";\n"
+         << "  labelloc=t;\n"
+         << "  nodesep=1;\n"
+         //<< "  splines=ortho;\n"
+         << "  concentrate=true;\n"
+         << "  label=\"<" << boost::core::demangle(typeid(t).name()) << ">" << t.getName() << "\";\n"
+         << "  node [style=\"filled,rounded\", shape=box, fontsize=\"9\", fontname=\"sans\"];\n"
+         << "  edge [labelfontsize=\"6\", fontsize=\"9\", fontname=\"monospace\"];\n"
+         << "  \n";
+
+    for (auto &network : t.networkList) {
+        network.accept(*this);
+    }
+
+    for (auto &t : _triggerMap) {
+        stream() << t.second;
+    }
+
+    for (auto &t : _triggerConnections) {
+        stream() << t;
+    }
+
+    stream() << "}\n";
+}
+
+void VariableNetworkGraphDumpingVisitor::dispatch(const VariableNetworkNode& t) {
+    std::string nodeName = prefix() + detail::encodeDotNodeName(detail::nodeName(t));
+    stream() << nodeName << "[\n";
+    if (t.getMode() == UpdateMode::push)
+        stream() << "    fillcolor=\"#ff9900\";\n";
+    else if (t.getMode() == UpdateMode::poll)
+        stream() << "    fillcolor=\"#b4d848\";\n";
+    else {
+        stream() << "    fillcolor=\"#ffffff\";\n";
+    }
+
+    stream() << "    label=\"" << detail::nodeName(t) << "\\n";
+
+    VariableNetworkNodeDumpingVisitor::dispatch(t);
+
+    stream() << "\"]\n";
+}
+} // namespace ChimeraTK
diff --git a/src/VariableNetworkNode.cc b/src/VariableNetworkNode.cc
index 650ca43f349203ed9f86e1c572f71ce6ac1668be..95919c3b0afa8282a6669ce65b0919ac687a6686 100644
--- a/src/VariableNetworkNode.cc
+++ b/src/VariableNetworkNode.cc
@@ -5,12 +5,12 @@
  *      Author: Martin Hierholzer
  */
 
-#include <libxml++/libxml++.h>
-
 #include "VariableNetworkNode.h"
 #include "VariableNetwork.h"
 #include "Application.h"
 #include "EntityOwner.h"
+#include "Visitor.h"
+#include "VariableNetworkNodeDumpingVisitor.h"
 
 namespace ChimeraTK {
 
@@ -125,104 +125,8 @@ namespace ChimeraTK {
 
   /*********************************************************************************************************************/
 
-  void VariableNetworkNode::dump(std::ostream& stream) const {
-    if(pdata->type == NodeType::Application) stream << " type = Application ('" << pdata->name << "')";
-    if(pdata->type == NodeType::ControlSystem) stream << " type = ControlSystem ('" << pdata->publicName << "')";
-    if(pdata->type == NodeType::Device) stream << " type = Device (" << pdata->deviceAlias << ": " << pdata->registerName << ")";
-    if(pdata->type == NodeType::TriggerReceiver) stream << " type = TriggerReceiver";
-    if(pdata->type == NodeType::Constant) stream << " type = Constant";
-    if(pdata->type == NodeType::invalid) stream << " type = **invalid**";
-
-    if(pdata->mode == UpdateMode::push) stream << " pushing";
-    if(pdata->mode == UpdateMode::poll) stream << " polling";
-
-    stream << " data type: " << pdata->valueType->name();
-    stream << " length: " << pdata->nElements;
-
-    stream << " [ptr: " << &(*pdata) << "]";
-
-    stream << " { ";
-    for(auto &tag : pdata->tags) stream << tag << " ";
-    stream << "}";
-
-    stream << std::endl;
-}
-
-  /*********************************************************************************************************************/
-
-  void VariableNetworkNode::createXML(xmlpp::Element *rootElement) const {
-    if(pdata->type != NodeType::ControlSystem) return;
-
-    // Create the directory for the path name in the XML document with all parent directories, if not yet existing:
-    // First split the publication name into components and loop over each component. For each component, try to find
-    // the directory node and create it it does not exist. After the loop, the "current" will point to the Element
-    // representing the directory.
-
-    // strip the variable name from the path
-    mtca4u::RegisterPath directory(pdata->publicName);
-    directory--;
-
-    // the namespace map is needed to properly refer to elements with an xpath expression in xmlpp::Element::find()
-    /// @todo TODO move this somewhere else, or at least take the namespace URI from a common place!
-    xmlpp::Node::PrefixNsMap nsMap{{"ac", "https://github.com/ChimeraTK/ApplicationCore"}};
-
-    // go through each directory path component
-    xmlpp::Element *current = rootElement;
-    for(auto pathComponent : directory.getComponents()) {
-      // find directory for this path component in the current directory
-      std::string xpath = std::string("ac:directory[@name='")+pathComponent+std::string("']");
-      auto list = current->find(xpath, nsMap);
-      if(list.size() == 0) {  // not found: create it
-        xmlpp::Element *newChild = current->add_child("directory");
-        newChild->set_attribute("name",pathComponent);
-        current = newChild;
-      }
-      else {
-        assert(list.size() == 1);
-        current = dynamic_cast<xmlpp::Element*>(list[0]);
-        assert(current != nullptr);
-      }
-    }
-
-    // now add the variable to the directory
-    xmlpp::Element *variable = current->add_child("variable");
-    mtca4u::RegisterPath pathName(pdata->publicName);
-    auto pathComponents = pathName.getComponents();
-
-    // set the name attribute
-    variable->set_attribute("name",pathComponents[pathComponents.size()-1]);
-
-    // add sub-element containing the data type
-    std::string dataTypeName{"unknown"};
-    if(pdata->network->getValueType() == typeid(int8_t)) { dataTypeName = "int8"; }
-    else if(pdata->network->getValueType() == typeid(uint8_t)) { dataTypeName = "uint8"; }
-    else if(pdata->network->getValueType() == typeid(int16_t)) { dataTypeName = "int16"; }
-    else if(pdata->network->getValueType() == typeid(uint16_t)) { dataTypeName = "uint16"; }
-    else if(pdata->network->getValueType() == typeid(int32_t)) { dataTypeName = "int32"; }
-    else if(pdata->network->getValueType() == typeid(uint32_t)) { dataTypeName = "uint32"; }
-    else if(pdata->network->getValueType() == typeid(float)) { dataTypeName = "float"; }
-    else if(pdata->network->getValueType() == typeid(double)) { dataTypeName = "double"; }
-    else if(pdata->network->getValueType() == typeid(std::string)) { dataTypeName = "string"; }
-    xmlpp::Element *valueTypeElement = variable->add_child("value_type");
-    valueTypeElement->set_child_text(dataTypeName);
-
-    // add sub-element containing the data flow direction
-    std::string dataFlowName{"application_to_control_system"};
-    if(pdata->network->getFeedingNode() == *this) { dataFlowName = "control_system_to_application"; }
-    xmlpp::Element *directionElement = variable->add_child("direction");
-    directionElement->set_child_text(dataFlowName);
-
-    // add sub-element containing the engineering unit
-    xmlpp::Element *unitElement = variable->add_child("unit");
-    unitElement->set_child_text(pdata->network->getUnit());
-
-    // add sub-element containing the description
-    xmlpp::Element *descriptionElement = variable->add_child("description");
-    descriptionElement->set_child_text(pdata->network->getDescription());
-
-    // add sub-element containing the description
-    xmlpp::Element *nElementsElement = variable->add_child("numberOfElements");
-    nElementsElement->set_child_text(std::to_string(pdata->network->getFeedingNode().getNumberOfElements()));
+  void VariableNetworkNode::accept( Visitor<VariableNetworkNode>& visitor) const {
+      visitor.dispatch(*this);
   }
 
   /*********************************************************************************************************************/
@@ -347,6 +251,13 @@ namespace ChimeraTK {
 
   /*********************************************************************************************************************/
 
+  void VariableNetworkNode::dump(std::ostream& stream) const {
+      VariableNetworkNodeDumpingVisitor visitor(stream, " ");
+      visitor.dispatch(*this);
+  }
+
+  /*********************************************************************************************************************/
+
   bool VariableNetworkNode::hasOwner() const {
     return pdata->network != nullptr;
   }
diff --git a/src/VariableNetworkNodeDumpingVisitor.cc b/src/VariableNetworkNodeDumpingVisitor.cc
new file mode 100644
index 0000000000000000000000000000000000000000..fc8d7ae5b305537c222fc27b6d6f9aba47b2d355
--- /dev/null
+++ b/src/VariableNetworkNodeDumpingVisitor.cc
@@ -0,0 +1,45 @@
+
+#include "VariableNetworkNodeDumpingVisitor.h"
+#include "VariableNetworkNode.h"
+
+namespace ChimeraTK {
+
+VariableNetworkNodeDumpingVisitor::VariableNetworkNodeDumpingVisitor(std::ostream &stream, const std::string& separator)
+    : Visitor<ChimeraTK::VariableNetworkNode> ()
+    , PushableStream(stream)
+    , _separator(separator) {}
+
+void VariableNetworkNodeDumpingVisitor::dispatch(const VariableNetworkNode& t) {
+    if(t.getType() == NodeType::Application) stream() << " type = Application ('" << t.getQualifiedName() << "')";
+    if(t.getType() == NodeType::ControlSystem) stream() << " type = ControlSystem ('" << t.getPublicName() << "')";
+    if(t.getType() == NodeType::Device) stream() << " type = Device (" << t.getDeviceAlias() << ": " << t.getRegisterName() << ")";
+    if(t.getType() == NodeType::TriggerReceiver) stream() << " type = TriggerReceiver";
+    if(t.getType() == NodeType::Constant) stream() << " type = Constant";
+    if(t.getType() == NodeType::invalid) stream() << " type = **invalid**";
+
+    if(t.getMode() == UpdateMode::push) stream() << _separator << "pushing";
+    if(t.getMode() == UpdateMode::poll) stream() << _separator << "polling";
+    stream() << _separator;
+
+    stream() << "data type: " << boost::core::demangle(t.getValueType().name());
+    stream() << _separator;
+    stream() << "length: " << t.getNumberOfElements();
+    stream() << _separator;
+
+    stream() << "[ptr: " << &(*(t.pdata)) << "]";
+    stream() << _separator;
+
+    stream() << "tags: [";
+    bool first = true;
+    for(auto &tag : t.getTags()) {
+        stream() << tag;
+        if (!first) stream() << ",";
+        first = false;
+    }
+    stream() << "]";
+    stream() << _separator;
+
+    stream() << std::endl;
+}
+
+} // namespace ChimeraTK
diff --git a/src/VisitorHelper.cc b/src/VisitorHelper.cc
new file mode 100644
index 0000000000000000000000000000000000000000..98c4ddcc66625e6849fb2da4b91282cbb8c47667
--- /dev/null
+++ b/src/VisitorHelper.cc
@@ -0,0 +1,23 @@
+#include "VisitorHelper.h"
+#include "VariableNetworkNode.h"
+
+namespace ChimeraTK {
+
+namespace detail {
+
+std::string encodeDotNodeName(std::string name) {
+    std::replace(name.begin(), name.end(), ':', 'c'); // colon
+    std::replace(name.begin(), name.end(), '/', 's'); // slash
+    std::replace(name.begin(), name.end(), '.', 'd'); // dot
+    std::replace(name.begin(), name.end(), ' ', '_'); // Generic space replacer
+    std::replace(name.begin(), name.end(), '*', 'a'); // asterisk
+
+    return name;
+}
+
+std::string nodeName(const VariableNetworkNode& node) {
+    return node.getQualifiedName().empty() ? node.getName() : node.getQualifiedName();
+}
+
+} // namespace detail
+} // namespace ChimeraTK
diff --git a/src/XMLGeneratorVisitor.cc b/src/XMLGeneratorVisitor.cc
new file mode 100644
index 0000000000000000000000000000000000000000..a47d215c06a049ec1ae72192edcc651dcb5512a0
--- /dev/null
+++ b/src/XMLGeneratorVisitor.cc
@@ -0,0 +1,114 @@
+#include "Application.h"
+#include "VariableNetworkNode.h"
+
+#include "XMLGeneratorVisitor.h"
+
+#include <mtca4u/RegisterPath.h>
+#include <libxml++/libxml++.h>
+
+#include <cassert>
+
+namespace ChimeraTK {
+XMLGeneratorVisitor::XMLGeneratorVisitor()
+    : Visitor<ChimeraTK::Application, ChimeraTK::VariableNetworkNode>()
+    , _doc(std::make_shared<xmlpp::Document>())
+    , _rootElement(_doc->create_root_node("application", "https://github.com/ChimeraTK/ApplicationCore"))
+{
+}
+
+void XMLGeneratorVisitor::save(const std::string &fileName) {
+    _doc->write_to_file_formatted(fileName);
+}
+
+void XMLGeneratorVisitor::dispatch(const Application& app) {
+    _rootElement->set_attribute("name", app.getName());
+    for (auto &network : app.networkList) {
+        network.check();
+
+        auto feeder = network.getFeedingNode();
+        feeder.accept(*this);
+
+        for (auto &consumer : network.getConsumingNodes()) {
+            consumer.accept(*this);
+        }
+    }
+}
+
+void XMLGeneratorVisitor::dispatch(const VariableNetworkNode &node) {
+    if(node.getType() != NodeType::ControlSystem) return;
+
+    // Create the directory for the path name in the XML document with all parent directories, if not yet existing:
+    // First split the publication name into components and loop over each component. For each component, try to find
+    // the directory node and create it it does not exist. After the loop, the "current" will point to the Element
+    // representing the directory.
+
+    // strip the variable name from the path
+    mtca4u::RegisterPath directory(node.getPublicName());
+    directory--;
+
+    // the namespace map is needed to properly refer to elements with an xpath expression in xmlpp::Element::find()
+    /// @todo TODO move this somewhere else, or at least take the namespace URI from a common place!
+    xmlpp::Node::PrefixNsMap nsMap{{"ac", "https://github.com/ChimeraTK/ApplicationCore"}};
+
+    // go through each directory path component
+    xmlpp::Element *current = _rootElement;
+    for(auto pathComponent : directory.getComponents()) {
+      // find directory for this path component in the current directory
+      std::string xpath = std::string("ac:directory[@name='")+pathComponent+std::string("']");
+      auto list = current->find(xpath, nsMap);
+      if(list.size() == 0) {  // not found: create it
+        xmlpp::Element *newChild = current->add_child("directory");
+        newChild->set_attribute("name",pathComponent);
+        current = newChild;
+      }
+      else {
+        assert(list.size() == 1);
+        current = dynamic_cast<xmlpp::Element*>(list[0]);
+        assert(current != nullptr);
+      }
+    }
+
+    // now add the variable to the directory
+    xmlpp::Element *variable = current->add_child("variable");
+    mtca4u::RegisterPath pathName(node.getPublicName());
+    auto pathComponents = pathName.getComponents();
+
+    // set the name attribute
+    variable->set_attribute("name",pathComponents[pathComponents.size()-1]);
+
+    // add sub-element containing the data type
+    std::string dataTypeName{"unknown"};
+    auto &owner = node.getOwner();
+    auto &type = owner.getValueType();
+    if(type == typeid(int8_t)) { dataTypeName = "int8"; }
+    else if(type == typeid(uint8_t)) { dataTypeName = "uint8"; }
+    else if(type == typeid(int16_t)) { dataTypeName = "int16"; }
+    else if(type == typeid(uint16_t)) { dataTypeName = "uint16"; }
+    else if(type == typeid(int32_t)) { dataTypeName = "int32"; }
+    else if(type == typeid(uint32_t)) { dataTypeName = "uint32"; }
+    else if(type == typeid(float)) { dataTypeName = "float"; }
+    else if(type == typeid(double)) { dataTypeName = "double"; }
+    else if(type == typeid(std::string)) { dataTypeName = "string"; }
+    xmlpp::Element *valueTypeElement = variable->add_child("value_type");
+    valueTypeElement->set_child_text(dataTypeName);
+
+    // add sub-element containing the data flow direction
+    std::string dataFlowName{"application_to_control_system"};
+    if(owner.getFeedingNode() == node) { dataFlowName = "control_system_to_application"; }
+    xmlpp::Element *directionElement = variable->add_child("direction");
+    directionElement->set_child_text(dataFlowName);
+
+    // add sub-element containing the engineering unit
+    xmlpp::Element *unitElement = variable->add_child("unit");
+    unitElement->set_child_text(owner.getUnit());
+
+    // add sub-element containing the description
+    xmlpp::Element *descriptionElement = variable->add_child("description");
+    descriptionElement->set_child_text(owner.getDescription());
+
+    // add sub-element containing the description
+    xmlpp::Element *nElementsElement = variable->add_child("numberOfElements");
+    nElementsElement->set_child_text(std::to_string(owner.getFeedingNode().getNumberOfElements()));
+}
+
+} // namespace ChimeraTK