diff --git a/GeoModelTools/CMakeLists.txt b/GeoModelTools/CMakeLists.txt
index bed80fb75f6f943b19353885f3239e9281857724..1f7861e775cd44706a8c2957b54673b7bc8cf4f8 100644
--- a/GeoModelTools/CMakeLists.txt
+++ b/GeoModelTools/CMakeLists.txt
@@ -48,6 +48,7 @@ include( GNUInstallDirs )
 # Set up the build of the libraries of the project.
 add_subdirectory( GeoModelXML )
 add_subdirectory( GeoModelXMLParser )
+add_subdirectory( GeoModelXMLDumper )
 add_subdirectory( GeoModelJSONParser )
 add_subdirectory( ExpressionEvaluator )
 add_subdirectory( GMCAT )
diff --git a/GeoModelTools/GeoModelXML/GeoModelXml/GeoModelXml/GmxUtil.h b/GeoModelTools/GeoModelXML/GeoModelXml/GeoModelXml/GmxUtil.h
index e149501c677c4f71a229f77a360f80f347a525c8..9e44cd9d99e2484f3771f9798c9ed44ad813060f 100644
--- a/GeoModelTools/GeoModelXML/GeoModelXml/GeoModelXml/GmxUtil.h
+++ b/GeoModelTools/GeoModelXML/GeoModelXml/GeoModelXml/GmxUtil.h
@@ -1,5 +1,5 @@
 /*
-  Copyright (C) 2002-2017 CERN for the benefit of the ATLAS collaboration
+  Copyright (C) 2002-2024 CERN for the benefit of the ATLAS collaboration
 */
 
 #ifndef GEO_MODEL_XML_GMXUTIL_H
@@ -54,6 +54,8 @@
 #include "GeoModelXml/shape/MakeSubtraction.h"
 #include "GeoModelXml/shape/MakeShaperef.h"
 #include "GeoModelXml/shape/AddPlane.h"
+#include "GeoModelXml/shape/MakeShapeShift.h"
+
 
 #include "GeoModelXml/PositionIndex.h"
 //#include "GeoModelXml/SensitiveId.h"
@@ -114,6 +116,7 @@ public:
         MakeUnion onion; // union is reserved
         MakeSubtraction subtraction;
         MakeShaperef shaperef;
+        MakeShapeShift shapeshift;
         MakeTransformation transformation;
         MakeTransformationref transformationref;
 //
diff --git a/GeoModelTools/GeoModelXML/GeoModelXml/GeoModelXml/shape/MakeShapeShift.h b/GeoModelTools/GeoModelXML/GeoModelXml/GeoModelXml/shape/MakeShapeShift.h
new file mode 100644
index 0000000000000000000000000000000000000000..c13c214c730b63c58e0f70a3f08d1313cd142d11
--- /dev/null
+++ b/GeoModelTools/GeoModelXML/GeoModelXml/GeoModelXml/shape/MakeShapeShift.h
@@ -0,0 +1,28 @@
+/*
+  Copyright (C) 2002-2024 CERN for the benefit of the ATLAS collaboration
+*/
+
+#ifndef GEO_MODEL_XML_MAKE_SHAPESHIFT_H
+#define GEO_MODEL_XML_MAKE_SHAPESHIFT_H
+#include <xercesc/util/XercesDefs.hpp>
+
+#include "GeoModelXml/Element2GeoItem.h"
+//
+//    Create and return a HepRotation3d. Caller must delete it.
+//
+
+XERCES_CPP_NAMESPACE_BEGIN
+class DOMElement;
+XERCES_CPP_NAMESPACE_END
+
+
+
+class GmxUtil;
+
+class MakeShapeShift: public Element2GeoItem {
+public:
+    MakeShapeShift() = default;
+    virtual GeoIntrusivePtr<RCBase> make(const xercesc::DOMElement *element, GmxUtil &gmxUtil) const override;
+};
+
+#endif // MAKE_ROTATION_H
diff --git a/GeoModelTools/GeoModelXML/GeoModelXml/data/geomodel_v0.dtd b/GeoModelTools/GeoModelXML/GeoModelXml/data/geomodel_v0.dtd
index dd3443183937b493945eb4d9e777814864e709fb..90bb859d8269990966d7307ba1bf640600ffc7b1 100644
--- a/GeoModelTools/GeoModelXML/GeoModelXml/data/geomodel_v0.dtd
+++ b/GeoModelTools/GeoModelXML/GeoModelXml/data/geomodel_v0.dtd
@@ -65,7 +65,7 @@
     <!ELEMENT addindex EMPTY>
         <!ATTLIST addindex name ID #REQUIRED>
 
-  <!ELEMENT shapes ((box|cons|generictrap|para|pcon|pgon|trap|tube|tubs|trd|intersection|subtraction|union|simplepolygonbrep|ellipticaltube|twistedtrap|torus)+)>
+  <!ELEMENT shapes ((box|cons|generictrap|para|pcon|pgon|trap|tube|tubs|trd|intersection|subtraction|union|simplepolygonbrep|ellipticaltube|twistedtrap|torus|shapeshift)+)>
     <!-- All shapes allowed in GeoModel manual. Same name, parameters, parameter order, but always lower case -->
 
     <!ELEMENT box EMPTY>
@@ -168,6 +168,9 @@
     <!ELEMENT intersection (shaperef, (transformation|transformationref), shaperef)>
       <!ATTLIST intersection name ID #REQUIRED>
 
+    <!ELEMENT shapeshift ( (shaperef|transformation|transformationref)+)>
+      <!ATTLIST shapeshift name ID #REQUIRED>
+
     <!ELEMENT subtraction (shaperef, (transformation|transformationref), shaperef)>
       <!ATTLIST subtraction name ID #REQUIRED>
 
@@ -268,7 +271,7 @@
     <!ATTLIST assemblyref ref    IDREF        #REQUIRED
                           zeroid (true|false) "false">
 
-  <!ELEMENT multicopy (transformation, (transform|logvolref|assemblyref))>
+  <!ELEMENT multicopy ( (transformation|transformationref), (transform|logvolref|assemblyref))>
     <!-- Removed logvol and assembly from multicopy content. There is no particular reason
          why they shouldn't be allowed there. But in practice one uses refs, and forbidding them simplifies coding
          the index stuff. If you need them, put them back. Got rid of transformref; never used. -->
diff --git a/GeoModelTools/GeoModelXML/GeoModelXml/src/GmxUtil.cxx b/GeoModelTools/GeoModelXML/GeoModelXml/src/GmxUtil.cxx
index f9e5ff1d31d488c5b1b92d1d5126818fc935449d..2445aae348daec2800166213c02b6f5b17aa8d5d 100644
--- a/GeoModelTools/GeoModelXML/GeoModelXml/src/GmxUtil.cxx
+++ b/GeoModelTools/GeoModelXML/GeoModelXml/src/GmxUtil.cxx
@@ -85,7 +85,8 @@ GmxUtil::GmxUtil(GmxInterface &gmxInterface):
     geoItemRegistry.enregister("shaperef",  &tagHandler.shaperef);
     geoItemRegistry.enregister("transformation",  &tagHandler.transformation);
     geoItemRegistry.enregister("transformationref",  &tagHandler.transformationref);
-
+    geoItemRegistry.enregister("shapeshift", &tagHandler.shapeshift);
+    
     tagHandler.addplane.gmxUtil=this;
 }
 
diff --git a/GeoModelTools/GeoModelXML/GeoModelXml/src/MakeShapeShift.cxx b/GeoModelTools/GeoModelXML/GeoModelXml/src/MakeShapeShift.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..540b8b75249fabfdaaeb405acd11e29d5fbdeeab
--- /dev/null
+++ b/GeoModelTools/GeoModelXML/GeoModelXml/src/MakeShapeShift.cxx
@@ -0,0 +1,62 @@
+/*
+  Copyright (C) 2002-2024 CERN for the benefit of the ATLAS collaboration
+*/
+#include "GeoModelXml/shape/MakeShapeShift.h"
+
+#include "OutputDirector.h"
+#include <string>
+#include <xercesc/dom/DOM.hpp>
+
+#include "GeoModelKernel/GeoShapeShift.h"
+#include "GeoModelKernel/GeoTransform.h"
+
+#include "xercesc/util/XMLString.hpp"
+#include "GeoModelXml/GmxUtil.h"
+
+#include "GeoModelHelpers/TransformSorter.h"
+#include "GeoModelHelpers/throwExcept.h"
+#include "GeoModelHelpers/GeoShapeUtils.h"
+
+#include "xercesc/util/XMLString.hpp"
+#include "GeoModelXml/GmxUtil.h"
+
+using namespace xercesc;
+
+GeoIntrusivePtr<RCBase> MakeShapeShift::make(const xercesc::DOMElement *element, GmxUtil &gmxUtil) const {
+    
+    unsigned int elementIndex = 0;
+    GeoShape* shape{nullptr};
+    GeoTrf::Transform3D hepXf{GeoTrf::Transform3D::Identity()};
+    for (DOMNode *child = element->getFirstChild(); child != nullptr; child = child->getNextSibling()) {
+       if (child->getNodeType() != DOMNode::ELEMENT_NODE) { 
+            continue;
+       }
+      switch (elementIndex) {
+        case 0: { // First element is first shaperef
+            shape = static_cast<GeoShape*>(gmxUtil.tagHandler.shaperef.process(dynamic_cast<DOMElement*> (child), gmxUtil));
+            break;
+        } case 1: { // Second element is transformation or transformationref
+            char *toRelease = XMLString::transcode(child->getNodeName());
+            std::string nodeName{toRelease};
+            XMLString::release(&toRelease);
+            const GeoTransform *geoXf = (nodeName == "transformation")
+              ? static_cast<const GeoTransform *>( gmxUtil.tagHandler.transformation.process(dynamic_cast<DOMElement *>(child), gmxUtil))
+              : static_cast<const GeoTransform *>( gmxUtil.tagHandler.transformationref.process(dynamic_cast<DOMElement *>(child), gmxUtil));
+            hepXf = geoXf->getTransform();
+            break;
+        } default: // More than 3 elements?
+            THROW_EXCEPTION("Only transofrmation & geoshaperef are accepted");
+      }
+      ++elementIndex;        
+    }
+    if (!shape) {
+        THROW_EXCEPTION("No shape has been given");
+    }
+    
+    static const GeoTrf::TransformSorter sorter{};
+    if (!sorter.compare(GeoTrf::Transform3D::Identity(), hepXf)) {
+        return shape;
+    }    
+    return cacheShape(make_intrusive<GeoShapeShift>(shape, hepXf));
+}
+
diff --git a/GeoModelTools/GeoModelXMLDumper/CMakeLists.txt b/GeoModelTools/GeoModelXMLDumper/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..d7d6408c99f18dc7385b594d3820d907171409f8
--- /dev/null
+++ b/GeoModelTools/GeoModelXMLDumper/CMakeLists.txt
@@ -0,0 +1,63 @@
+# Copyright (C) 2002-2020 CERN for the benefit of the ATLAS collaboration
+
+# Find the header and source files.
+file( GLOB SOURCES src/*.cxx )
+file( GLOB HEADERS GeoModelXMLDumper/*.h GeoModelXMLDumper/*.icc )
+
+# Create the library.
+add_library( GeoModelXMLDumper SHARED ${HEADERS} ${SOURCES} )
+target_link_libraries( GeoModelXMLDumper PUBLIC GeoModelCore::GeoModelKernel 
+                                                GeoModelCore::GeoModelHelpers )
+target_include_directories( GeoModelXMLDumper PUBLIC
+   $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
+   $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}> )
+source_group( "GeoModelXMLDumper" FILES ${HEADERS} )
+source_group( "src" FILES ${SOURCES} )
+set_target_properties( GeoModelXMLDumper PROPERTIES
+   VERSION ${PROJECT_VERSION}
+   SOVERSION ${PROJECT_VERSION_MAJOR} )
+
+# Set up an alias with the same name that you would get by "finding" a pre-built
+# version of the library.
+add_library( GeoModelTools::GeoModelXMLDumper ALIAS GeoModelXMLDumper )
+
+# Install the library.
+install(TARGETS GeoModelXMLDumper
+    EXPORT ${PROJECT_NAME}-export
+    RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
+            COMPONENT Runtime
+    LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
+            COMPONENT          Runtime
+            NAMELINK_COMPONENT Development   # Requires CMake 3.12
+    ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
+            COMPONENT Development
+    PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/GeoModelXMLDumper
+            COMPONENT Development
+    INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
+)
+
+install( FILES ${HEADERS}
+   DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/GeoModelXMLDumper
+   COMPONENT Development )
+
+
+
+# Declare the package's executable.
+add_executable( dumpGeoXML util/main.cxx)
+target_link_libraries( dumpGeoXML PRIVATE GeoModelCore::GeoModelKernel
+                                          GeoModelCore::GeoModelHelpers
+                                          GeoModelIO::GeoModelRead
+                                          GeoModelTools::GeoModelXMLDumper)
+
+# Tweak how debug information should be attached to the executable, in Debug
+# builds.
+if( "${CMAKE_BUILD_TYPE}" STREQUAL "Debug" AND
+   "${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU" )
+   target_compile_options( dumpGeoXML PRIVATE "-gdwarf-2" )
+endif()
+
+# Install the executable.
+install( TARGETS dumpGeoXML
+   EXPORT ${PROJECT_NAME}-export
+   RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
+   COMPONENT Runtime )
diff --git a/GeoModelTools/GeoModelXMLDumper/GeoModelXMLDumper/GeoXMLDumper.h b/GeoModelTools/GeoModelXMLDumper/GeoModelXMLDumper/GeoXMLDumper.h
new file mode 100644
index 0000000000000000000000000000000000000000..f5e3dd0bf95d9683552d41375fe8676486f9afdf
--- /dev/null
+++ b/GeoModelTools/GeoModelXMLDumper/GeoModelXMLDumper/GeoXMLDumper.h
@@ -0,0 +1,93 @@
+/*
+  Copyright (C) 2002-2024 CERN for the benefit of the ATLAS collaboration
+*/
+#ifndef GEOMODELXMLDUMPER_GEOXMLDUMPER_H
+#define GEOMODELXMLDUMPER_GEOXMLDUMPER_H
+
+#include "GeoModelKernel/GeoVPhysVol.h"
+#include "GeoModelKernel/GeoTransform.h"
+
+#include "GeoModelKernel/GeoIntrusivePtr.h"
+#include "GeoModelHelpers/GeoShapeSorter.h"
+#include "GeoModelHelpers/GeoPhysVolSorter.h"
+#include "GeoModelHelpers/TransformSorter.h"
+
+
+#include <string>
+#include <map>
+
+/** @brief: The GeoXmlDumper class traverses the GeoModel tree and translates its content 
+ *          into plain GeoModelXML
+ * 
+*/
+
+class GeoXMLDumper {
+public:
+    GeoXMLDumper(const std::string& outDir,
+                 const std::string& systemName,
+                 const std::string& xmlLayoutFile);
+
+    bool publishWorld(PVConstLink world);
+
+
+
+
+private:
+     std::string m_outDir{};
+     std::string m_systemName{};
+     std::string m_xmlLayoutFile{};
+
+     void countVolume(PVConstLink volume);
+
+private:
+     /// @brief 
+     /// @param outStream 
+     /// @param physVol 
+     /// @param volName 
+     /// @param indentation 
+     /// @return 
+     std::string publishPhysVol(std::ostream& outStream,
+                                PVConstLink physVol,
+                                const std::string& volName,
+                                unsigned int indentation = 0);
+
+     std::string publishShape(std::ostream& shapeStream,
+                              GeoIntrusivePtr<const GeoShape> shape,
+                              const std::string& shapeName);
+
+    /// @brief  Creates the actual header file of the GeoModel description
+    /// @param ostr 
+    using topVolWithPathList = std::vector<std::pair<std::string, std::string>>;
+    void dumpGeoModelHeader(std::ostream& ostr,
+                            const topVolWithPathList& pubVolsWithFiles) const;
+    
+    void writeMaterials(std::ostream& ostr,
+                        unsigned int indentation = 0) const;
+
+
+    void dumpTransform(std::ostream& ostr,
+                       const GeoTrf::Transform3D& trans,
+                       const std::string& refName,
+                       unsigned int indentation = 0);
+
+    std::string uniqueNameTag(std::string nameTag,
+                              const std::string_view idToken);
+
+    using shapeMap = std::map<GeoIntrusivePtr<const GeoShape>, std::string, GeoShapeSorter>;
+    using physVolMap = std::map<PVConstLink, std::string, GeoPhysVolSorter>;
+    using transformMap = std::map<GeoTrf::Transform3D, std::string, GeoTrf::TransformSorter>;
+
+
+    using physVolCounter = std::map<PVConstLink, unsigned int, GeoPhysVolSorter>;    
+    using nameTagCounter = std::map<std::string, unsigned int>;
+
+    physVolMap m_physVolNameTags{};
+    shapeMap m_shapeNameTags{};
+    transformMap m_transformNameTags{};
+
+    nameTagCounter m_nameTagCounts{};
+    physVolCounter m_physVolCounts{};
+    
+};
+
+#endif
\ No newline at end of file
diff --git a/GeoModelTools/GeoModelXMLDumper/src/GeoXMLDumper.cxx b/GeoModelTools/GeoModelXMLDumper/src/GeoXMLDumper.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..f78ca6c1fbf78eeb88a74d19c43fb5dbcc867eda
--- /dev/null
+++ b/GeoModelTools/GeoModelXMLDumper/src/GeoXMLDumper.cxx
@@ -0,0 +1,539 @@
+/*
+  Copyright (C) 2002-2024 CERN for the benefit of the ATLAS collaboration
+*/
+#include "GeoModelXMLDumper/GeoXMLDumper.h"
+#include "GeoModelKernel/Units.h"
+#include "GeoModelKernel/GeoVolumeCursor.h"
+
+#include "GeoModelKernel/GeoShapeUnion.h"
+#include "GeoModelKernel/GeoShapeSubtraction.h"
+#include "GeoModelKernel/GeoShapeIntersection.h"
+#include "GeoModelKernel/GeoShapeShift.h"
+
+
+#include "GeoModelKernel/GeoCons.h"
+#include "GeoModelKernel/GeoBox.h"
+#include "GeoModelKernel/GeoEllipticalTube.h"
+#include "GeoModelKernel/GeoGenericTrap.h"
+#include "GeoModelKernel/GeoPara.h"
+#include "GeoModelKernel/GeoPcon.h"
+#include "GeoModelKernel/GeoPgon.h"
+#include "GeoModelKernel/GeoTorus.h"
+#include "GeoModelKernel/GeoTrap.h"
+#include "GeoModelKernel/GeoTrd.h"
+#include "GeoModelKernel/GeoTube.h"
+#include "GeoModelKernel/GeoTubs.h"
+#include "GeoModelKernel/GeoSimplePolygonBrep.h"
+
+#include "GeoModelHelpers/GeoShapeUtils.h"
+#include "GeoModelHelpers/getChildNodesWithTrf.h"
+#include "GeoModelHelpers/throwExcept.h"
+#include "GeoModelHelpers/TransformToStringConverter.h"
+#include "GeoModelHelpers/StringUtils.h"
+#include "GeoModelHelpers/FileUtils.h"
+
+#include <iomanip>
+#include <iostream>
+#include <limits>
+#include <fstream>
+#include <string_view>
+#include <functional>
+
+
+using namespace GeoStrUtils;
+using namespace GeoFileUtils;
+namespace {
+    constexpr unsigned int stdIndent = 4;
+    constexpr double transTolerance = 0.1 * GeoModelKernelUnits::micrometer;
+    constexpr double toDeg = 1./ GeoModelKernelUnits::deg;
+
+    std::string shapeRefTag(const std::string& shapeName, unsigned int nWhite) {
+        std::stringstream sstr{};
+        sstr<<whiteSpaces(nWhite)<<"<shaperef ref=\""<<shapeName<<"\" />";
+        return sstr.str();
+    }
+
+    std::string fileNameBody(const std::string_view path) {
+        const unsigned slashPos = path.rfind("/") + 1;
+        return std::string{path.substr(slashPos, path.rfind(".") - slashPos)};
+    }
+    static const GeoTrf::TransformSorter transformSorter{};
+}
+
+
+GeoXMLDumper::GeoXMLDumper(const std::string& outDir,
+                           const std::string& systemName,
+                           const std::string& xmlLayoutFile):
+    m_outDir{outDir},
+    m_systemName{systemName},
+    m_xmlLayoutFile{xmlLayoutFile} {
+        if (m_outDir.size() && m_outDir[m_outDir.size() -1] != '/') {
+            m_outDir+="/";
+        }
+}
+
+void GeoXMLDumper::countVolume(PVConstLink volume){
+    ++(m_physVolCounts[volume]);
+    GeoVolumeCursor cursor{volume};
+    while (!cursor.atEnd()) {
+        countVolume(cursor.getVolume());
+        cursor.next();
+    }
+}
+
+bool GeoXMLDumper::publishWorld(PVConstLink world) {
+    if (m_outDir.empty()) {
+        std::cerr<<"Please define an output directory "<<std::endl;
+        return false;
+    }
+    /// Count how many distinct volumes are in the world and also at how many nodes each volume appears
+    std::cout<<"Traverse the GeoModel tree to count the different physical volumes "<<std::endl;
+    countVolume(world);
+    {
+        unsigned int counts{0};
+        for (const auto& [vol, c] : m_physVolCounts) counts+=c;
+        std::cout<<"Found in total "<<m_physVolCounts.size()<<" different volumes used in "<<counts<<" nodes."<<std::endl;        
+    }
+    /// If the world only has one volume get to its child
+    while (world->getNChildVols() == 1)  {
+        world = world->getChildVol(0);
+    }
+
+    GeoVolumeCursor cursor{world};
+
+    topVolWithPathList publishedTops{};
+    while (!cursor.atEnd()) {
+        PVConstLink worldNode{cursor.getVolume()};
+        const std::string out_xmlDir = m_outDir + "volumes/";
+        if (!mkdir(out_xmlDir)) {
+            std::cerr<<"Failed to create directory "<<out_xmlDir<<std::endl;
+            return false;
+        }
+        const std::string out_xmlFile = out_xmlDir + 
+                                        replaceExpInString(cursor.getName()," ", "_") + ".xml";
+
+        std::ofstream outFile{out_xmlFile};
+        if (!outFile.good()) {
+            std::cerr<<"Failed to open "<<out_xmlFile<<std::endl;
+            return false;
+        }
+        const std::string pubVolume{publishPhysVol(outFile, cursor.getVolume(),  cursor.getName())};
+        publishedTops.emplace_back(std::make_pair(out_xmlFile, pubVolume));
+        cursor.next();
+    }
+
+    const std::string out_xmlFile = m_outDir+ m_systemName +".xml";
+
+    std::ofstream outFile{out_xmlFile};
+    if (!outFile.good()) {
+        std::cerr<<"Failed to open "<<out_xmlFile<<std::endl;
+        return false;
+    } 
+    dumpGeoModelHeader(outFile, publishedTops);
+    return true;
+}
+std::string GeoXMLDumper::uniqueNameTag(std::string nameTag,
+                                        const std::string_view idToken) {
+    nameTag = replaceExpInString(nameTag, " ", "_");
+    unsigned int& nameTagCounts{m_nameTagCounts[nameTag]};
+    if (nameTagCounts> 0) {
+        nameTag += std::string{idToken}+"No"+std::to_string(nameTagCounts);
+    }
+    ++nameTagCounts;
+    return nameTag;
+}
+std::string GeoXMLDumper::publishPhysVol(std::ostream& outStream,
+                                         PVConstLink physVol,
+                                         const std::string& volName,
+                                         unsigned int indentation) {
+    
+    physVolMap::const_iterator itr = m_physVolNameTags.find(physVol);
+    if (itr != m_physVolNameTags.end()) return itr->second;
+    
+    std::string nameTag{volName};
+
+    std::stringstream shapeStream{}, childStream{};
+ 
+    /// Retrieve the children together with their transformations
+    std::vector<GeoChildNodeWithTrf> children = getChildrenWithRef(physVol, true);
+    for (const GeoChildNodeWithTrf& child : children) {
+        /// Dump the transformation
+        
+        unsigned int childIndent = indentation + stdIndent;
+
+        const bool dumpTrf{child.isAlignable || transformSorter.compare(GeoTrf::Transform3D::Identity(), child.transform)};
+
+        if (dumpTrf) {
+            childStream<<whiteSpaces(indentation + stdIndent)
+                       <<"<transform name=\""<<uniqueNameTag(child.nodeName, "VolTrf")<<"\" ";
+            if (child.isAlignable)childStream<<"alignable=\"true\" ";
+            childStream<<">"<<std::endl;
+            
+            childIndent += stdIndent;
+            dumpTransform(childStream, child.transform, child.nodeName, childIndent);           
+        }
+        if (child.nCopies > 1) {
+            childStream<<whiteSpaces(childIndent)<<"<assembly name=\""
+                       <<uniqueNameTag(child.nodeName, "Assembly")<<"\">"<<std::endl;
+            childIndent +=stdIndent;
+            childStream<<whiteSpaces(childIndent)<<"<multicopy n=\""<<child.nCopies<<"\" "
+                       <<"name=\""<<uniqueNameTag(child.nodeName, "MultiCopy")<<"\">"<<std::endl;
+            childIndent+=stdIndent;
+            dumpTransform(childStream, child.inductionRule, child.nodeName, childIndent);
+        }
+        childStream<<whiteSpaces(childIndent)<<"<logvolref ref=\""<<publishPhysVol(outStream, child.volume, child.nodeName)<<"\" />"<<std::endl;
+        if (child.nCopies > 1) {
+            childIndent-=stdIndent;
+            childStream<<whiteSpaces(childIndent)<<"</multicopy>"<<std::endl;
+            childIndent-=stdIndent;
+            childStream<<whiteSpaces(childIndent)<<"</assembly>"<<std::endl;
+        }
+        if (dumpTrf) {
+            childStream<<whiteSpaces(childIndent - stdIndent)<<"</transform>"<<std::endl;
+        }
+        childStream<<std::endl;        
+    }
+    
+    const std::string shapeName = publishShape(shapeStream, physVol->getLogVol()->getShape(), nameTag);
+    const std::string pubShapes{shapeStream.str()};
+    if (pubShapes.size()) {
+        outStream<<"<shapes>"<<std::endl;
+        outStream<<pubShapes;
+        outStream<<"</shapes>"<<std::endl;
+    }
+    nameTag = uniqueNameTag(nameTag, "logVol");
+
+    outStream<<whiteSpaces(indentation)<<"<logvol name=\""<<nameTag<<"\" ";
+    const std::string matName = replaceExpInString(physVol->getLogVol()->getMaterial()->getName(), "::", "__dColon__");
+
+    outStream<<"shape=\""<<shapeName<<"\" material=\""<<matName<<"\" ";
+    if (children.empty()) {
+        outStream<<"/>"<<std::endl;
+    } else {
+        outStream<<">"<<std::endl;
+        outStream<<childStream.str();    
+        outStream<<whiteSpaces(indentation)<<"</logvol>"<<std::endl;
+    }
+    outStream<<std::endl<<std::endl;
+ 
+    auto insert_itr = m_physVolNameTags.insert(std::make_pair(physVol, nameTag));
+    if (!insert_itr.second) {
+        THROW_EXCEPTION("Failed to cache physical volume "<<physVol.get()<<" with shape "<<printGeoShape(physVol->getLogVol()->getShape())
+                       <<" as "<<nameTag<<std::endl<<"The volume "<<insert_itr.first->first.get()<<" with shape "
+                       <<printGeoShape(insert_itr.first->first->getLogVol()->getShape())<<" as "<<insert_itr.first->second
+                       <<" is blocking the way");
+    }
+
+    return insert_itr.first->second;
+}
+
+std::string GeoXMLDumper::publishShape(std::ostream& shapeStream,
+                                       GeoIntrusivePtr<const GeoShape> shape,
+                                       const std::string& shapeName) {
+    
+    /// The Volume has been already published before refer to it
+    shapeMap::const_iterator itr = m_shapeNameTags.find(shape);
+    if (itr != m_shapeNameTags.end()) return itr->second;
+    
+    
+    std::pair<const GeoShape*, const GeoShape*> operands = getOps(shape);
+    /// The shape is a boolean volume (GeoShapeUnion, Subtraction, Intersection)
+    if (operands.first && operands.second) {
+        publishShape(shapeStream, operands.first, shapeName);
+        publishShape(shapeStream, operands.second, shapeName);
+    } 
+    /// The shape is a  GeoShapeShift 
+    else if (operands.first) {
+        /// In fact there might be several Shape shifts in a row. Collapse them into a single shift
+        /// and publish the underlying shape
+        operands = getOps(compressShift(shape));
+        publishShape(shapeStream, operands.first, shapeName);
+    }
+    
+    /// Create a unique name tag
+    const std::string nameTag{uniqueNameTag(shapeName.size() ? shapeName : shape->type(), "Shape")};
+    /// Register the shape
+    const auto insert_itr = m_shapeNameTags.insert(std::make_pair(shape, nameTag));
+    if (!insert_itr.second) {
+        THROW_EXCEPTION("The insertion of shape "<<printGeoShape(shape)<<" as "<<nameTag<<" failed."<<std::endl<<
+                        "The shape "<<printGeoShape(insert_itr.first->first)<<" as "<<insert_itr.first->second
+                        << " has been added before. ");
+    }
+
+    auto dumpParameter = [&shapeStream](const std::string_view parName, 
+                                        const double value,
+                                        bool isAngle = false) {
+        
+        shapeStream<<parName<<"=\"";
+        if (isAngle) {
+            shapeStream<<value*toDeg<<"*deg\" ";
+        } else {
+            shapeStream<<value<<"\" ";
+        }
+    };
+
+    constexpr unsigned int indentation = stdIndent;
+    const unsigned int shapeID = shape->typeID();    
+    shapeStream<<whiteSpaces(indentation);
+    if (shapeID == GeoShapeUnion::getClassTypeID() ||
+        shapeID == GeoShapeSubtraction::getClassTypeID() ||
+        shapeID == GeoShapeIntersection::getClassTypeID()) {       
+        
+        const std::string xmlField = shapeID == GeoShapeUnion::getClassTypeID() ? "union" :
+                                     shapeID == GeoShapeSubtraction::getClassTypeID()? "subtraction" : "intersection";
+        shapeStream<<"<"<<xmlField<<" name=\""<<nameTag<<"\" >"<<std::endl;
+        shapeStream<<shapeRefTag(publishShape(shapeStream, operands.first, shapeName), indentation + stdIndent)<<std::endl;
+        if (operands.second->typeID() == GeoShapeShift::getClassTypeID()) {
+            GeoIntrusivePtr<const GeoShape> collapsedShift = compressShift(operands.second);
+            const GeoShapeShift* shift = dynamic_cast<const GeoShapeShift*>(collapsedShift.get());
+            dumpTransform(shapeStream, shift->getX(), shapeName, indentation + stdIndent);
+            shapeStream<<shapeRefTag(publishShape(shapeStream, shift->getOp(), shapeName), indentation + stdIndent)<<std::endl;
+        } else{
+            shapeStream<<shapeRefTag(publishShape(shapeStream, operands.second, shapeName), indentation + stdIndent)<<std::endl;
+        }
+        shapeStream<<whiteSpaces(indentation)<<"</"<<xmlField<<">"<<std::endl;
+    } else if (shapeID == GeoShapeShift::getClassTypeID()) {
+        shapeStream<<"<shapeshift name=\""<<nameTag<<"\" >"<<std::endl;
+        GeoIntrusivePtr<const GeoShape> collapsedShift = compressShift(shape);
+        const GeoShapeShift* shift = dynamic_cast<const GeoShapeShift*>(collapsedShift.get());
+        dumpTransform(shapeStream, shift->getX(), shapeName, indentation + stdIndent);        
+        shapeStream<<shapeRefTag(publishShape(shapeStream, operands.first, shapeName), 
+                                              indentation + stdIndent)<<std::endl;
+        shapeStream<<whiteSpaces(indentation)<<"</shapeshift>"<<std::endl;
+
+    } else if (shapeID == GeoBox::getClassTypeID()) {
+        shapeStream<<"<box name=\""<<nameTag<<"\" ";
+        const GeoBox* box = dynamic_cast<const GeoBox*>(shape.get());
+        dumpParameter("xhalflength", box->getXHalfLength());
+        dumpParameter("yhalflength", box->getYHalfLength());
+        dumpParameter("zhalflength", box->getZHalfLength());
+        shapeStream<<"/>"<<std::endl;
+    } else if (shapeID == GeoCons::getClassTypeID()) {
+        const GeoCons* cons = static_cast<const GeoCons*>(shape.get());
+        shapeStream<<"<cons name=\""<<nameTag<<"\" ";
+        dumpParameter("rmin1", cons->getRMin1());
+        dumpParameter("rmin2", cons->getRMin2());
+        dumpParameter("rmax1", cons->getRMax1());
+        dumpParameter("rmax2", cons->getRMax2());
+        dumpParameter("dz", cons->getDZ());
+        dumpParameter("sphi", cons->getSPhi(), true);
+        dumpParameter("dphi", cons->getDPhi(), true);
+        shapeStream<<"/>"<<std::endl;
+    } else if (shapeID == GeoEllipticalTube::getClassTypeID()) {
+        const GeoEllipticalTube* tube = static_cast<const GeoEllipticalTube*>(shape.get());
+        shapeStream<<"<ellipticaltube name=\""<<nameTag<<"\" ";
+        dumpParameter("xhalflength", tube->getXHalfLength());
+        dumpParameter("yhalflength", tube->getYHalfLength());
+        dumpParameter("zhalflength", tube->getZHalfLength());
+        shapeStream<<"/>"<<std::endl;
+    } else if (shapeID == GeoGenericTrap::getClassTypeID()) {
+        const GeoGenericTrap* trap = static_cast<const GeoGenericTrap*>(shape.get());
+        shapeStream<<"<generictrap name=\""<<nameTag<<"\" ";
+        dumpParameter("zhalflength", trap->getZHalfLength());
+        for (unsigned int v = 0; v < std::min(8lu, trap->getVertices().size()); ++v){
+            shapeStream<<"x"<<v<<"=\""<<trap->getVertices()[v].x()<<"\" ";
+            shapeStream<<"y"<<v<<"=\""<<trap->getVertices()[v].y()<<"\" ";
+        } 
+        shapeStream<<"/>"<<std::endl;
+    } else if (shapeID == GeoPara::getClassTypeID()) {
+        const GeoPara* para = dynamic_cast<const GeoPara*>(shape.get());
+        shapeStream<<"<para name=\""<<nameTag<<"\" ";
+        dumpParameter("xhalflength", para->getXHalfLength());
+        dumpParameter("yhalflength", para->getYHalfLength());
+        dumpParameter("zhalflength", para->getZHalfLength());
+        dumpParameter("alpha", para->getAlpha(), true);
+        dumpParameter("theta", para->getTheta(), true);
+        dumpParameter("phi", para->getPhi(), true);
+        shapeStream<<"/>"<<std::endl;
+    } else if (shapeID == GeoPcon::getClassTypeID()) {
+        const GeoPcon* pcon = dynamic_cast<const GeoPcon*>(shape.get());
+        shapeStream<<"<pcon name=\""<<nameTag<<"\" ";
+        dumpParameter("sphi", pcon->getSPhi(), true);
+        dumpParameter("dphi", pcon->getSPhi(), true);
+        shapeStream<<">"<<std::endl;
+        for (unsigned int plane = 0 ; plane < pcon->getNPlanes(); ++plane) {
+            shapeStream<<whiteSpaces(indentation + stdIndent);
+            shapeStream<<"<addplane ";
+            dumpParameter("zplane", pcon->getZPlane(plane));
+            dumpParameter("rminplane", pcon->getRMinPlane(plane));            
+            dumpParameter("rmaxplane", pcon->getRMaxPlane(plane));
+            shapeStream<<"/>"<<std::endl;
+        }        
+        shapeStream<<whiteSpaces(indentation)<<"</pcon>"<<std::endl;
+    } else if (shapeID == GeoPgon::getClassTypeID()) {
+        const GeoPgon* pgon = dynamic_cast<const GeoPgon*>(shape.get());
+        shapeStream<<"<pgon name=\""<<nameTag<<"\" ";
+        dumpParameter("sphi", pgon->getSPhi(), true);
+        dumpParameter("dphi", pgon->getSPhi(), true);
+        dumpParameter("nsides", pgon->getNSides());
+        for (unsigned int plane = 0 ; plane < pgon->getNPlanes(); ++plane) {
+            shapeStream<<whiteSpaces(indentation + stdIndent);
+            shapeStream<<"<plane ";
+            dumpParameter("zplane", pgon->getZPlane(plane));
+            dumpParameter("rminplane", pgon->getRMinPlane(plane));            
+            dumpParameter("rmaxplane", pgon->getRMaxPlane(plane));
+            shapeStream<<"/>"<<std::endl;
+        }        
+        shapeStream<<whiteSpaces(indentation)<<"</pgon>"<<std::endl;
+    } else if (shapeID == GeoTorus::getClassTypeID()) {
+        shapeStream<<"<torus name=\""<<nameTag<<"\" ";
+        const GeoTorus* torus{dynamic_cast<const GeoTorus*>(shape.get())};
+        dumpParameter("rmin", torus->getRMin());
+        dumpParameter("rmax", torus->getRMax());
+        dumpParameter("rtor", torus->getRTor());
+        dumpParameter("sphi", torus->getSPhi(), true);
+        dumpParameter("dphi", torus->getDPhi(), true);
+        shapeStream<<"/>"<<std::endl;
+    } else if (shapeID == GeoTrap::getClassTypeID()) {
+        shapeStream<<"<trap name=\""<<nameTag<<"\" ";
+        const GeoTrap* trap{dynamic_cast<const GeoTrap*>(shape.get())};
+        dumpParameter("zhalflength", trap->getZHalfLength());
+        dumpParameter("theta", trap->getTheta(), true);
+        dumpParameter("phi", trap->getPhi(), true);
+        dumpParameter("dydzn", trap->getDydzn());
+        dumpParameter("dxdyndzn", trap->getDxdyndzn());
+        dumpParameter("dxdypdzn", trap->getDxdypdzn());
+        dumpParameter("angleydzn", trap->getAngleydzn(), true);
+        dumpParameter("dydzp", trap->getDydzn());
+        dumpParameter("dxdyndzp", trap->getDxdyndzp());
+        dumpParameter("dxdypdzp", trap->getDxdypdzp());
+        dumpParameter("angleydzp", trap->getAngleydzp());
+        shapeStream<<"/>"<<std::endl;
+    } else if (shapeID == GeoTrd::getClassTypeID()) {
+        shapeStream<<"<trd name=\""<<nameTag<<"\" ";
+        const GeoTrd* trd = dynamic_cast<const GeoTrd*>(shape.get());
+        dumpParameter("xhalflength1", trd->getXHalfLength1());
+        dumpParameter("xhalflength2", trd->getXHalfLength2());
+        dumpParameter("yhalflength1", trd->getYHalfLength1());
+        dumpParameter("yhalflength2", trd->getYHalfLength2());
+        dumpParameter("zhalflength", trd->getZHalfLength());
+        shapeStream<<"/>"<<std::endl;
+    } else if (shapeID == GeoTube::getClassTypeID()) {
+        const GeoTube* tube = dynamic_cast<const GeoTube*>(shape.get());
+        shapeStream<<"<tube name=\""<<nameTag<<"\" ";
+        dumpParameter("rmin", tube->getRMin());
+        dumpParameter("rmax", tube->getRMax());
+        dumpParameter("zhalflength", tube->getZHalfLength());
+        shapeStream<<"/>"<<std::endl;
+    } else if (shapeID == GeoTubs::getClassTypeID()) {
+        const GeoTubs* tube = dynamic_cast<const GeoTubs*>(shape.get());
+        shapeStream<<"<tubs name=\""<<nameTag<<"\" ";
+        dumpParameter("rmin", tube->getRMin());
+        dumpParameter("rmax", tube->getRMax());
+        dumpParameter("zhalflength", tube->getZHalfLength());
+        dumpParameter("sphi", tube->getSPhi(), true);
+        dumpParameter("dphi", tube->getDPhi(), true);
+        shapeStream<<"/>"<<std::endl;
+    } else if(shapeID == GeoSimplePolygonBrep::getClassTypeID()) {
+        const GeoSimplePolygonBrep* brep = dynamic_cast<const GeoSimplePolygonBrep*>(shape.get());
+        shapeStream<<"<simplepolygonbrep name=\""<<nameTag<<"\" ";
+        dumpParameter("zhalflength" , brep->getDZ());
+        const unsigned int N = brep->getNVertices();
+        shapeStream<<"xpoints=\""<<chainUp<double>(N, [brep](unsigned int v){return brep->getXVertex(v); }, ";")<<"\" ";
+        shapeStream<<"ypoints=\""<<chainUp<double>(N, [brep](unsigned int v){return brep->getYVertex(v); }, ";")<<"\" ";        
+        shapeStream<<"/>"<<std::endl;
+    } else {
+        THROW_EXCEPTION("The shape "<<printGeoShape(shape)<<"is not implemented.");
+    }
+    return nameTag;
+}
+
+void GeoXMLDumper::dumpTransform(std::ostream& ostr,
+                                 const GeoTrf::Transform3D& trans,
+                                 const std::string& name,
+                                 unsigned int indentation) {    
+
+
+    transformMap::const_iterator itr = m_transformNameTags.find(trans);
+    if (!name.empty() && itr != m_transformNameTags.end()) {
+        ostr<<whiteSpaces(indentation)<<"<transformationref  ref=\""<<itr->second<<"\" />"<<std::endl;
+        return;
+    }
+
+
+    std::stringstream transstr{};
+    transstr<< std::setiosflags(std::ios::fixed) << std::setprecision(3);
+    std::string nameTag{replaceExpInString(name," ","_")};
+    if (nameTag.size()) {
+        nameTag = uniqueNameTag(nameTag, "Trf");
+        auto insert_itr = m_transformNameTags.insert(std::make_pair(trans, nameTag));
+        if(!insert_itr.second) {
+            THROW_EXCEPTION("Failed to insert transformation "<<GeoTrf::toString(trans));
+        }
+    }
+    transstr<<whiteSpaces(indentation)<<"<transformation ";
+    if (nameTag.size()) transstr<<"name=\""<<nameTag<<"\" ";   
+    transstr<<">"<<std::endl;
+   
+    if (!transformSorter.compare(trans, GeoTrf::Transform3D::Identity())) {
+        transstr<<whiteSpaces(indentation + stdIndent)
+                <<"<translation x=\"0\" y=\"0\" z=\"0\" />"<<std::endl;
+    } else {
+        const GeoTrf::CoordEulerAngles angles = GeoTrf::getCoordRotationAngles(trans);
+        const GeoTrf::Vector3D translation = trans.translation();
+        /// Dump the translation
+        if (translation.dot(translation) > transTolerance) {
+            transstr<<whiteSpaces(indentation +stdIndent)<<"<translation ";
+            if (std::abs(translation.x()) > transTolerance) {
+                transstr<<"x=\""<<translation.x()<<"\" ";
+            }
+            if (std::abs(translation.y()) > transTolerance) {
+                transstr<<"y=\""<<translation.y()<<"\" ";
+            }
+            if (std::abs(translation.z()) > transTolerance) {
+                transstr<<"z=\""<<translation.z()<<"\" ";
+            }
+            transstr<<"/>"<<std::endl;
+        }
+        transstr<<std::setiosflags(std::ios::fixed) << std::setprecision(2);
+
+
+        /// Dump the rotations
+        if (angles.alpha) {
+            transstr<<whiteSpaces(indentation+stdIndent)<<"<rotation xcos=\"1\" angle=\""
+                    <<angles.alpha*toDeg<<"*deg\" />"<<std::endl;
+        }
+        if (angles.beta) {
+            transstr<<whiteSpaces(indentation+stdIndent)
+                        <<"<rotation ycos=\"1\" angle=\""<<angles.beta*toDeg<<"*deg\" />"<<std::endl;
+        }
+        if (angles.gamma) {
+            transstr<<whiteSpaces(indentation+stdIndent)
+                    <<"<rotation zcos=\"1\" angle=\""<<angles.gamma*toDeg<<"*deg\" />"<<std::endl;
+        }
+    }
+    transstr<<whiteSpaces(indentation)<<"</transformation>"<<std::endl;
+    ostr<<transstr.str();
+}
+void GeoXMLDumper::dumpGeoModelHeader(std::ostream& fileStream,
+                                      const topVolWithPathList& pubVolsWithFiles) const {
+
+    fileStream<<"<?xml version=\"1.0\" encoding=\"UTF-8\" ?>"<<std::endl;
+    fileStream<<"<!DOCTYPE geomodel SYSTEM \""<<m_xmlLayoutFile<<"\" [ "<<std::endl;
+    
+    std::vector<std::string> entities{};
+    for (const auto& [fileName, pubVolName] : pubVolsWithFiles) {
+        entities.emplace_back(fileNameBody(fileName));
+        fileStream<<whiteSpaces(stdIndent)<<"<!ENTITY "<<entities.back();
+        fileStream<<" SYSTEM \""<<fileName<<"\" >"<<std::endl;
+    }
+
+    fileStream<<"]>"<<std::endl<<std::endl<<std::endl;
+
+    fileStream<<"<geomodel name=\""<<m_systemName<<"\" version=\"1.0\" >"<<std::endl;
+    fileStream<<whiteSpaces(stdIndent)<<"<defines>"<<std::endl;
+    fileStream<<whiteSpaces(2*stdIndent)<<"<var name =\"deg\" value=\""<<GeoModelKernelUnits::deg<<"\" />"<<std::endl;
+    fileStream<<whiteSpaces(stdIndent)<<"</defines>"<<std::endl<<std::endl<<std::endl;
+
+    for (const std::string& entity : entities) {
+        fileStream<<whiteSpaces(stdIndent)<<"&"<<entity<<";"<<std::endl;
+    }
+    fileStream<<std::endl<<std::endl;
+
+    fileStream<<whiteSpaces(stdIndent)<<"<addbranch>"<<std::endl;
+    for (const auto& [fileName, pubVolName] : pubVolsWithFiles) {
+        fileStream<<whiteSpaces(2*stdIndent)<<"<logvolref ref=\""<<pubVolName<<"\" />"<<std::endl;
+    }
+    fileStream<<whiteSpaces(stdIndent)<<"</addbranch>"<<std::endl;
+    fileStream<<"</geomodel>"<<std::endl;
+}
\ No newline at end of file
diff --git a/GeoModelTools/GeoModelXMLDumper/util/main.cxx b/GeoModelTools/GeoModelXMLDumper/util/main.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..3b37523d430e3e785dbe8cf3a522bf60ce530f73
--- /dev/null
+++ b/GeoModelTools/GeoModelXMLDumper/util/main.cxx
@@ -0,0 +1,105 @@
+/*
+  Copyright (C) 2002-2024 CERN for the benefit of the ATLAS collaboration
+*/
+
+#include "GeoModelHelpers/defineWorld.h"
+#include "GeoModelKernel/GeoGeometryPluginLoader.h"
+#include "GeoModelXMLDumper/GeoXMLDumper.h"
+#include "GeoModelHelpers/FileUtils.h"
+#include "GeoModelHelpers/StringUtils.h"
+
+
+#include <vector>
+#include <string>
+#include <iostream>
+
+using namespace GeoFileUtils;
+using namespace GeoStrUtils;
+
+
+int main(int argc, char ** argv) {
+     const  std::string stdDtdFile{"geomodel_v0.dtd"};
+
+    std::vector<std::string> plugins{};
+    std::string outDir{};
+    std::string detectorName{"ForkLiftGarage"};
+    std::string xmlLayoutFile{};
+
+    for (int k =1 ; k < argc; ++k) {
+        const std::string the_arg{argv[k]};
+        if (the_arg=="-p" || the_arg=="--plugin") {
+           bool added{false};
+           while (k+1 < argc) {
+                const std::string toLoad{argv[k+1]};
+                if (toLoad[0] == '-') break;
+                plugins.push_back(toLoad);
+                ++k;
+                added = true;
+           }
+           if (!added) {
+                std::cerr<<"Please give at least one follow-up argument to "<<the_arg<<std::endl;
+                return EXIT_FAILURE;
+           }
+        } else if (the_arg == "-o" || the_arg=="--outDir"){
+            if (k+1 < argc) {
+                outDir = argv[k+1];
+                ++k;
+            } else {
+                std::cerr<<"Please give one follow-up argument to "<<the_arg<<std::endl;
+                return EXIT_FAILURE;
+            }
+        } else {
+            std::cerr<<"Unknown argument "<<the_arg<<std::endl;
+            return EXIT_FAILURE;
+        }
+    }
+    if(plugins.empty()) {
+        std::cerr<<"Please specify at least one plugin via -p / --plugin "<<std::endl;
+        return EXIT_FAILURE;
+    }
+
+    if (outDir.empty()) {
+        std::cerr<<"Please specify an output file via -o "<<std::endl;
+        return EXIT_FAILURE;
+    }
+    if (!mkdir(outDir)) {
+        std::cerr<<"Failed to create directory "<<outDir<<std::endl;
+        return EXIT_FAILURE;
+    }
+    if (xmlLayoutFile.empty()) {
+        const std::string ldEnvVar = resolveEnviromentVariables("${LD_LIBRARY_PATH}");
+        const std::vector<std::string> ldLibPaths{tokenize(ldEnvVar, ":")};
+        for (const std::string& ld_lib : ldLibPaths){
+            const std::string sharePath{ld_lib+"/../share/"};
+            if (!doesDirectoryExist(sharePath)) {
+                continue;
+            }            
+            //  
+            const std::vector<std::string> dtdPaths{findFile(sharePath, stdDtdFile)};
+            
+            if (dtdPaths.size() && !copyFile(dtdPaths[0], outDir + "/" + stdDtdFile)) {
+                return EXIT_FAILURE;
+            }
+            xmlLayoutFile = stdDtdFile;
+        }
+        if (xmlLayoutFile.empty()) {
+            return EXIT_FAILURE;
+        }
+    }
+    GeoIntrusivePtr<GeoVPhysVol> world{createGeoWorld()};
+
+    for (const std::string& load_me : plugins) {
+        std::cout<<"Load plugin "<<load_me<<std::endl;
+        GeoGeometryPluginLoader loader;
+        std::unique_ptr<GeoVGeometryPlugin> factory{loader.load(load_me)};
+        if (!factory) {
+          std::cerr << "Could not load plugin " << load_me << std::endl;
+          return EXIT_FAILURE;
+        }
+        factory->create(world);
+    }
+    GeoXMLDumper dumper{outDir, detectorName, xmlLayoutFile};
+    dumper.publishWorld(world);
+
+    return EXIT_SUCCESS;
+}
\ No newline at end of file