From 7300dcd674d4b8cee465e7376b5b4574a848924d Mon Sep 17 00:00:00 2001
From: Benedikt Seeger <benedikt.seeger@ptb.de>
Date: Mon, 24 Feb 2025 08:55:38 +0100
Subject: [PATCH] added code

---
 .gitignore                                    |  2 +
 dcc-viewer/.idea/workspace.xml                | 95 +++++++++++++++++++
 dcc-viewer/package-lock.json                  | 33 +++++++
 dcc-viewer/package.json                       | 10 +-
 dcc-viewer/public/index.html                  | 23 +++--
 dcc-viewer/public/styles.css                  | 34 ++++++-
 .../src/components/accordionComponent.js      | 20 ++++
 dcc-viewer/src/components/tabComponent.js     | 29 ++++++
 dcc-viewer/src/globalData.js                  |  4 +-
 dcc-viewer/src/main.js                        | 17 ++--
 dcc-viewer/src/ui/adminRenderer.js            | 72 ++++++++++++++
 dcc-viewer/src/ui/jsonTreeViewer.js           | 11 +++
 dcc-viewer/src/ui/measurementRenderer.js      | 63 ++++++++++++
 dcc-viewer/src/xmlToJson.js                   |  9 +-
 project_snapshot.json                         | 20 ++++
 project_snapshotter.py                        | 40 ++++++++
 16 files changed, 453 insertions(+), 29 deletions(-)
 create mode 100644 .gitignore
 create mode 100644 dcc-viewer/.idea/workspace.xml
 create mode 100644 dcc-viewer/package-lock.json
 create mode 100644 dcc-viewer/src/components/accordionComponent.js
 create mode 100644 dcc-viewer/src/components/tabComponent.js
 create mode 100644 dcc-viewer/src/ui/adminRenderer.js
 create mode 100644 dcc-viewer/src/ui/jsonTreeViewer.js
 create mode 100644 dcc-viewer/src/ui/measurementRenderer.js
 create mode 100644 project_snapshot.json
 create mode 100644 project_snapshotter.py

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..7880498
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+
+dcc-viewer/node_modules/
diff --git a/dcc-viewer/.idea/workspace.xml b/dcc-viewer/.idea/workspace.xml
new file mode 100644
index 0000000..85843e9
--- /dev/null
+++ b/dcc-viewer/.idea/workspace.xml
@@ -0,0 +1,95 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="AutoImportSettings">
+    <option name="autoReloadType" value="SELECTIVE" />
+  </component>
+  <component name="ChangeListManager">
+    <list default="true" id="c3d759c9-e9f8-41d4-b9b5-ef12db68e70c" name="Changes" comment="">
+      <change afterPath="$PROJECT_DIR$/src/components/accordionComponent.js" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/src/components/tabComponent.js" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/src/ui/adminRenderer.js" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/src/ui/jsonTreeViewer.js" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/src/ui/measurementRenderer.js" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/package.json" beforeDir="false" afterPath="$PROJECT_DIR$/package.json" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/public/index.html" beforeDir="false" afterPath="$PROJECT_DIR$/public/index.html" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/public/styles.css" beforeDir="false" afterPath="$PROJECT_DIR$/public/styles.css" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/globalData.js" beforeDir="false" afterPath="$PROJECT_DIR$/src/globalData.js" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/main.js" beforeDir="false" afterPath="$PROJECT_DIR$/src/main.js" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/xmlToJson.js" beforeDir="false" afterPath="$PROJECT_DIR$/src/xmlToJson.js" afterDir="false" />
+    </list>
+    <option name="SHOW_DIALOG" value="false" />
+    <option name="HIGHLIGHT_CONFLICTS" value="true" />
+    <option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
+    <option name="LAST_RESOLUTION" value="IGNORE" />
+  </component>
+  <component name="FileTemplateManagerImpl">
+    <option name="RECENT_TEMPLATES">
+      <list>
+        <option value="JavaScript File" />
+      </list>
+    </option>
+  </component>
+  <component name="Git.Settings">
+    <option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$/.." />
+  </component>
+  <component name="ProjectColorInfo">{
+  &quot;associatedIndex&quot;: 6
+}</component>
+  <component name="ProjectId" id="2tLWdqlIz5X4SCSwK5EjWvfiZtG" />
+  <component name="ProjectViewState">
+    <option name="hideEmptyMiddlePackages" value="true" />
+    <option name="showLibraryContents" value="true" />
+  </component>
+  <component name="PropertiesComponent"><![CDATA[{
+  "keyToString": {
+    "JavaScript Debug.Debug Vite.executor": "Run",
+    "JavaScript Debug.dccViewer.executor": "Debug",
+    "JavaScript Debug.index.html.executor": "Run",
+    "Node.js.vite.config.js.executor": "Run",
+    "RunOnceActivity.ShowReadmeOnStart": "true",
+    "RunOnceActivity.git.unshallow": "true",
+    "git-widget-placeholder": "main",
+    "last_opened_file_path": "/home/seeger01/Downloads/de-6.4.jar",
+    "node.js.detected.package.eslint": "true",
+    "node.js.detected.package.tslint": "true",
+    "node.js.selected.package.eslint": "(autodetect)",
+    "node.js.selected.package.tslint": "(autodetect)",
+    "nodejs_package_manager_path": "npm",
+    "settings.editor.selected.configurable": "http.proxy",
+    "ts.external.directory.path": "/home/seeger01/Downloads/WebStorm-243.24978.60/plugins/javascript-plugin/jsLanguageServicesImpl/external",
+    "vue.rearranger.settings.migration": "true"
+  }
+}]]></component>
+  <component name="RunManager">
+    <configuration name="dccViewer" type="JavascriptDebugType" uri="http://localhost:5173/">
+      <method v="2" />
+    </configuration>
+  </component>
+  <component name="SharedIndexes">
+    <attachedChunks>
+      <set>
+        <option value="bundled-js-predefined-d6986cc7102b-76f8388c3a79-JavaScript-WS-243.24978.60" />
+      </set>
+    </attachedChunks>
+  </component>
+  <component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
+  <component name="TaskManager">
+    <task active="true" id="Default" summary="Default task">
+      <changelist id="c3d759c9-e9f8-41d4-b9b5-ef12db68e70c" name="Changes" comment="" />
+      <created>1740131925655</created>
+      <option name="number" value="Default" />
+      <option name="presentableId" value="Default" />
+      <updated>1740131925655</updated>
+      <workItem from="1740131927264" duration="102000" />
+      <workItem from="1740132052064" duration="10000" />
+      <workItem from="1740132146315" duration="24000" />
+      <workItem from="1740132175271" duration="89000" />
+      <workItem from="1740158794192" duration="1155000" />
+      <workItem from="1740380748268" duration="2676000" />
+    </task>
+    <servers />
+  </component>
+  <component name="TypeScriptGeneratedFilesManager">
+    <option name="version" value="3" />
+  </component>
+</project>
\ No newline at end of file
diff --git a/dcc-viewer/package-lock.json b/dcc-viewer/package-lock.json
new file mode 100644
index 0000000..dda2d33
--- /dev/null
+++ b/dcc-viewer/package-lock.json
@@ -0,0 +1,33 @@
+{
+    "name": "dcc-viewer",
+    "version": "1.0.0",
+    "lockfileVersion": 3,
+    "requires": true,
+    "packages": {
+        "": {
+            "name": "dcc-viewer",
+            "version": "1.0.0",
+            "dependencies": {
+                "xml-js": "^1.6.11"
+            }
+        },
+        "node_modules/sax": {
+            "version": "1.4.1",
+            "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz",
+            "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==",
+            "license": "ISC"
+        },
+        "node_modules/xml-js": {
+            "version": "1.6.11",
+            "resolved": "https://registry.npmjs.org/xml-js/-/xml-js-1.6.11.tgz",
+            "integrity": "sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==",
+            "license": "MIT",
+            "dependencies": {
+                "sax": "^1.2.4"
+            },
+            "bin": {
+                "xml-js": "bin/cli.js"
+            }
+        }
+    }
+}
diff --git a/dcc-viewer/package.json b/dcc-viewer/package.json
index 019e6c9..68a489d 100644
--- a/dcc-viewer/package.json
+++ b/dcc-viewer/package.json
@@ -1,6 +1,10 @@
 {
     "name": "dcc-viewer",
     "version": "1.0.0",
-    "scripts": { "dev": "vite" },
-    "dependencies": { "xml2js": "^0.4.23" }
-}
\ No newline at end of file
+    "scripts": {
+        "dev": "vite"
+    },
+    "dependencies": {
+        "xml-js": "^1.6.11"
+    }
+}
diff --git a/dcc-viewer/public/index.html b/dcc-viewer/public/index.html
index 0c36cb1..758d4ea 100644
--- a/dcc-viewer/public/index.html
+++ b/dcc-viewer/public/index.html
@@ -1,18 +1,17 @@
 <!DOCTYPE html>
-<html lang='en'>
+<html lang="en">
 <head>
-    <meta charset='UTF-8'>
+    <meta charset="UTF-8">
     <title>DCC Viewer</title>
-    <script type='module' src='/src/main.js'></script>
-    <link rel='stylesheet' href='/styles.css'>
+    <script type="module" src="/src/main.js"></script>
+    <link rel="stylesheet" href="/public/styles.css">
 </head>
 <body>
-    <input type='file' id='file-input' accept='.xml'>
-    <button id='theme-toggle'>Toggle Theme</button>
-    <select id='language-select'>
-        <option value='en'>English</option>
-        <option value='de'>German</option>
-    </select>
-    <div id='json-container'></div>
+<h1>DCC Viewer</h1>
+
+<input type="file" id="file-input" accept=".xml">
+
+<div id="admin-data-container"></div>
+<div id="measurement-data-container"></div>
 </body>
-</html>
\ No newline at end of file
+</html>
diff --git a/dcc-viewer/public/styles.css b/dcc-viewer/public/styles.css
index 732e263..79e2888 100644
--- a/dcc-viewer/public/styles.css
+++ b/dcc-viewer/public/styles.css
@@ -1,2 +1,32 @@
-body { font-family: Arial, sans-serif; }
-.dark-mode { background-color: black; color: white; }
\ No newline at end of file
+.accordion-header {
+    background-color: #f0f0f0;
+    border: none;
+    padding: 10px;
+    cursor: pointer;
+    width: 100%;
+    text-align: left;
+}
+
+.accordion-content {
+    display: none;
+    padding: 10px;
+}
+
+.accordion-content.open {
+    display: block;
+}
+
+.tabs {
+    display: flex;
+    gap: 10px;
+}
+
+.tabs button {
+    padding: 5px 10px;
+    cursor: pointer;
+}
+
+.tab-content {
+    border: 1px solid #ddd;
+    padding: 10px;
+}
diff --git a/dcc-viewer/src/components/accordionComponent.js b/dcc-viewer/src/components/accordionComponent.js
new file mode 100644
index 0000000..bc77325
--- /dev/null
+++ b/dcc-viewer/src/components/accordionComponent.js
@@ -0,0 +1,20 @@
+export function createAccordion(title, contentElement) {
+    const container = document.createElement('div');
+    container.classList.add('accordion');
+
+    const header = document.createElement('button');
+    header.textContent = title;
+    header.classList.add('accordion-header');
+
+    const content = document.createElement('div');
+    content.classList.add('accordion-content');
+    content.appendChild(contentElement);
+
+    header.addEventListener('click', () => {
+        content.classList.toggle('open');
+    });
+
+    container.appendChild(header);
+    container.appendChild(content);
+    return container;
+}
diff --git a/dcc-viewer/src/components/tabComponent.js b/dcc-viewer/src/components/tabComponent.js
new file mode 100644
index 0000000..51b08be
--- /dev/null
+++ b/dcc-viewer/src/components/tabComponent.js
@@ -0,0 +1,29 @@
+export function createTabs(tabsData) {
+    const container = document.createElement('div');
+    container.classList.add('tabs-container');
+
+    const tabs = document.createElement('div');
+    tabs.classList.add('tabs');
+
+    const contentContainer = document.createElement('div');
+    contentContainer.classList.add('tab-content');
+
+    tabsData.forEach((tab, index) => {
+        const button = document.createElement('button');
+        button.textContent = tab.title;
+        button.addEventListener('click', () => {
+            contentContainer.innerHTML = '';
+            contentContainer.appendChild(tab.content);
+        });
+
+        tabs.appendChild(button);
+
+        if (index === 0) {
+            contentContainer.appendChild(tab.content);
+        }
+    });
+
+    container.appendChild(tabs);
+    container.appendChild(contentContainer);
+    return container;
+}
diff --git a/dcc-viewer/src/globalData.js b/dcc-viewer/src/globalData.js
index c15978e..d3b8d90 100644
--- a/dcc-viewer/src/globalData.js
+++ b/dcc-viewer/src/globalData.js
@@ -1 +1,3 @@
-export const globalData = {};
\ No newline at end of file
+export const globalData = {
+    dcc: null // This will hold the parsed DCC JSON data
+};
\ No newline at end of file
diff --git a/dcc-viewer/src/main.js b/dcc-viewer/src/main.js
index 8b19e28..29b595d 100644
--- a/dcc-viewer/src/main.js
+++ b/dcc-viewer/src/main.js
@@ -1,8 +1,9 @@
 import { convertXMLToJSON } from './xmlToJson.js';
-import { JSONTreeViewer } from './components/JSONTreeViewer.js';
+import { globalData } from './globalData.js';
+import { renderAdministrativeData } from './ui/adminRenderer.js';
+import { renderMeasurementResults } from './ui/measurementRenderer.js';
 
 const fileInput = document.getElementById('file-input');
-const container = document.getElementById('json-container');
 
 fileInput.addEventListener('change', async (event) => {
     const file = event.target.files?.[0];
@@ -11,10 +12,12 @@ fileInput.addEventListener('change', async (event) => {
     const reader = new FileReader();
     reader.onload = async () => {
         const xmlString = reader.result;
-        const jsonData = await convertXMLToJSON(xmlString);
-        const viewer = new JSONTreeViewer(jsonData);
-        container.innerHTML = '';
-        container.appendChild(viewer.render());
+        globalData.dcc = await convertXMLToJSON(xmlString); // Store DCC globally
+
+        console.log("DCC Data Loaded:", globalData.dcc);
+
+        renderAdministrativeData(globalData.dcc.administrativeData);
+        renderMeasurementResults(globalData.dcc.measurementResults);
     };
     reader.readAsText(file);
-});
\ No newline at end of file
+});
diff --git a/dcc-viewer/src/ui/adminRenderer.js b/dcc-viewer/src/ui/adminRenderer.js
new file mode 100644
index 0000000..857d6a4
--- /dev/null
+++ b/dcc-viewer/src/ui/adminRenderer.js
@@ -0,0 +1,72 @@
+import { createAccordion } from '../components/accordionComponent.js';
+import { JSONTreeViewer } from './jsonTreeViewer.js';
+import { globalData } from '../globalData.js';
+
+export function renderAdministrativeData() {
+    const container = document.getElementById('admin-data-container');
+    container.innerHTML = ''; // Clear previous content
+
+    const dccRoot = globalData.dcc?.['dcc:digitalCalibrationCertificate'];
+    if (!dccRoot) {
+        container.innerHTML = "<p>No Digital Calibration Certificate Found</p>";
+        return;
+    }
+
+    const adminData = dccRoot?.['dcc:administrativeData'];
+    if (!adminData) {
+        container.innerHTML = "<p>No Administrative Data Found</p>";
+        return;
+    }
+
+    // 🔹 SOFTWARE SECTION
+    const softwareContainer = document.createElement('div');
+    const softwareList = (adminData?.['dcc:dccSoftware']?.['dcc:software'] || []).map(software => {
+        return `
+            <div>
+                <strong>${software?.['dcc:name']?.['dcc:content'] || 'Unknown Software'}</strong>
+                (v${software?.['dcc:release'] || 'N/A'}) - ${software?.['dcc:type'] || 'N/A'}
+            </div>
+        `;
+    }).join('');
+    softwareContainer.innerHTML = `<h3>Software Used</h3>${softwareList}`;
+
+    // 🔹 CORE DATA SECTION
+    const coreData = adminData?.['dcc:coreData'] || {};
+    const coreDataContainer = document.createElement('div');
+    coreDataContainer.innerHTML = `
+        <h3>Core Data</h3>
+        <p><strong>Country:</strong> ${coreData?.['dcc:countryCodeISO3166_1']?._text || 'N/A'}</p>
+        <p><strong>Languages:</strong> ${Array.isArray(coreData?.['dcc:usedLangCodeISO639_1'])
+        ? coreData['dcc:usedLangCodeISO639_1'].map(lang => lang._text).join(', ')
+        : coreData?.['dcc:usedLangCodeISO639_1']?._text || 'N/A'}</p>
+        <p><strong>Unique Identifier:</strong> ${coreData?.['dcc:uniqueIdentifier']?._text || 'N/A'}</p>
+        <p><strong>Issue Date:</strong> ${coreData?.['dcc:issueDate']?._text || 'N/A'}</p>
+    `;
+
+    // 🔹 CALIBRATION LAB
+    const labInfo = adminData?.['dcc:calibrationLaboratory']?.['dcc:contact'] || {};
+    const labContainer = document.createElement('div');
+    labContainer.innerHTML = `
+        <h3>Calibration Laboratory</h3>
+        <p><strong>Name:</strong> ${labInfo?.['dcc:name']?.['dcc:content']?._text || 'N/A'}</p>
+        <p><strong>Email:</strong> ${labInfo?.['dcc:eMail']?._text || 'N/A'}</p>
+        <p><strong>Phone:</strong> ${labInfo?.['dcc:phone']?._text || 'N/A'}</p>
+    `;
+
+    // 🔹 RESPONSIBLE PERSONS
+    const responsiblePersons = (adminData?.['dcc:respPersons']?.['dcc:respPerson'] || []).map(person => {
+        return `<p>${person?.['dcc:person']?.['dcc:name']?.['dcc:content']?._text || 'Unknown Person'}
+                 ${person?.['dcc:mainSigner'] ? '(Main Signer)' : ''}</p>`;
+    }).join('');
+    const responsibleContainer = document.createElement('div');
+    responsibleContainer.innerHTML = `<h3>Responsible Persons</h3>${responsiblePersons}`;
+
+    // 🔹 ADD SECTIONS TO ACCORDION
+    container.appendChild(createAccordion("Software Used", softwareContainer));
+    container.appendChild(createAccordion("Core Data", coreDataContainer));
+    container.appendChild(createAccordion("Calibration Laboratory", labContainer));
+    container.appendChild(createAccordion("Responsible Persons", responsibleContainer));
+
+    // 🔹 JSON DEBUG VIEW
+    container.appendChild(createAccordion("Raw JSON Data (Debug)", new JSONTreeViewer(adminData).render()));
+}
diff --git a/dcc-viewer/src/ui/jsonTreeViewer.js b/dcc-viewer/src/ui/jsonTreeViewer.js
new file mode 100644
index 0000000..fdb88bb
--- /dev/null
+++ b/dcc-viewer/src/ui/jsonTreeViewer.js
@@ -0,0 +1,11 @@
+export class JSONTreeViewer {
+    constructor(data) {
+        this.data = data;
+    }
+
+    render() {
+        const container = document.createElement('pre');
+        container.textContent = JSON.stringify(this.data, null, 2);
+        return container;
+    }
+}
diff --git a/dcc-viewer/src/ui/measurementRenderer.js b/dcc-viewer/src/ui/measurementRenderer.js
new file mode 100644
index 0000000..4b64b37
--- /dev/null
+++ b/dcc-viewer/src/ui/measurementRenderer.js
@@ -0,0 +1,63 @@
+import { createTabs } from '../components/tabComponent.js';
+import { JSONTreeViewer } from './jsonTreeViewer.js';
+import { globalData } from '../globalData.js';
+
+export function renderMeasurementResults() {
+    const container = document.getElementById('measurement-data-container');
+    container.innerHTML = ''; // Clear previous content
+
+    const dccRoot = globalData.dcc?.['dcc:digitalCalibrationCertificate'];
+    if (!dccRoot) {
+        container.innerHTML = "<p>No Digital Calibration Certificate Found</p>";
+        return;
+    }
+
+    const measurements = dccRoot?.['dcc:measurementResults'];
+    if (!measurements || !measurements['dcc:measurementResult']) {
+        container.innerHTML = "<p>No Measurement Results Found</p>";
+        return;
+    }
+
+    const tabs = (Array.isArray(measurements['dcc:measurementResult']) ?
+            measurements['dcc:measurementResult'] : [measurements['dcc:measurementResult']]
+    ).map((result, index) => {
+        const resultContainer = document.createElement('div');
+
+        // 🔹 Measurement Methods
+        const methods = result?.['dcc:usedMethods']?.['dcc:usedMethod'] || [];
+        const methodsList = (Array.isArray(methods) ? methods : [methods]).map(method => `
+            <p><strong>${method?.['dcc:name']?.['dcc:content']?._text || 'Unknown Method'}</strong>: 
+            ${method?.['dcc:description']?.['dcc:content']?._text || 'No description'}</p>
+        `).join('');
+
+        // 🔹 Measurement Equipment
+        const equipments = result?.['dcc:measuringEquipments']?.['dcc:measuringEquipment'] || [];
+        const equipmentList = (Array.isArray(equipments) ? equipments : [equipments]).map(eq => `
+            <p><strong>${eq?.['dcc:name']?.['dcc:content']?._text || 'Unknown Equipment'}</strong> 
+            - ${eq?.['dcc:model']?._text || 'N/A'}</p>
+        `).join('');
+
+        // 🔹 Measurement Results
+        const results = result?.['dcc:results']?.['dcc:result'] || [];
+        const resultsList = (Array.isArray(results) ? results : [results]).map(res => `
+            <h4>${res?.['dcc:name']?.['dcc:content']?._text || 'Unknown Result'}</h4>
+            <pre>${JSON.stringify(res['dcc:data'], null, 2)}</pre>
+        `).join('');
+
+        resultContainer.innerHTML = `
+            <h3>Measurement Methods</h3>${methodsList}
+            <h3>Equipment Used</h3>${equipmentList}
+            <h3>Results</h3>${resultsList}
+        `;
+
+        return {
+            title: `Measurement ${index + 1}`,
+            content: resultContainer
+        };
+    });
+
+    container.appendChild(createTabs(tabs));
+
+    // 🔹 JSON DEBUGGING VIEW
+    container.appendChild(createTabs([{ title: "Raw JSON", content: new JSONTreeViewer(measurements).render() }]));
+}
diff --git a/dcc-viewer/src/xmlToJson.js b/dcc-viewer/src/xmlToJson.js
index b308fc3..b25a854 100644
--- a/dcc-viewer/src/xmlToJson.js
+++ b/dcc-viewer/src/xmlToJson.js
@@ -1,5 +1,6 @@
-import { parseStringPromise } from 'xml2js';
+import { xml2json } from 'xml-js';
 
-export async function convertXMLToJSON(xmlString) {
-    return parseStringPromise(xmlString, { explicitArray: false, mergeAttrs: true });
-}
\ No newline at end of file
+export function convertXMLToJSON(xmlString) {
+    const jsonData = xml2json(xmlString, { compact: true, spaces: 2 });
+    return JSON.parse(jsonData);
+}
diff --git a/project_snapshot.json b/project_snapshot.json
new file mode 100644
index 0000000..3099818
--- /dev/null
+++ b/project_snapshot.json
@@ -0,0 +1,20 @@
+{
+    "src/main.js": "import { convertXMLToJSON } from './xmlToJson.js';\nimport { globalData } from './globalData.js';\nimport { renderAdministrativeData } from './ui/adminRenderer.js';\nimport { renderMeasurementResults } from './ui/measurementRenderer.js';\n\nconst fileInput = document.getElementById('file-input');\n\nfileInput.addEventListener('change', async (event) => {\n    const file = event.target.files?.[0];\n    if (!file) return;\n\n    const reader = new FileReader();\n    reader.onload = async () => {\n        const xmlString = reader.result;\n        globalData.dcc = await convertXMLToJSON(xmlString); // Store DCC globally\n\n        console.log(\"DCC Data Loaded:\", globalData.dcc);\n\n        renderAdministrativeData(globalData.dcc.administrativeData);\n        renderMeasurementResults(globalData.dcc.measurementResults);\n    };\n    reader.readAsText(file);\n});\n",
+    "src/xmlToJson.js": "import { xml2json } from 'xml-js';\n\nexport function convertXMLToJSON(xmlString) {\n    const jsonData = xml2json(xmlString, { compact: true, spaces: 2 });\n    return JSON.parse(jsonData);\n}\n",
+    "src/idRegistry.js": "export const idRegistry = {};",
+    "src/globalData.js": "export const globalData = {\n    dcc: null // This will hold the parsed DCC JSON data\n};",
+    "src/globalOptions.js": "export const globalOptions = { preferredLanguage: 'en' };",
+    "src/viewerRegistry.js": "import { JSONTreeViewer } from './components/JSONTreeViewer.js';\n\nexport function getViewerForElement() {\n    return JSONTreeViewer;\n}",
+    "src/utils.js": "export function getLocalizedText(elements) {\n    if (!Array.isArray(elements)) return elements;\n    return elements[0]._ || '';\n}",
+    "src/components/BaseViewer.js": "export class BaseViewer {\n    constructor(sectionData) {\n        this.sectionData = sectionData;\n    }\n    render() {\n        throw new Error('render() must be implemented in subclasses');\n    }\n}",
+    "src/components/JSONTreeViewer.js": "import { BaseViewer } from './BaseViewer.js';\n\nexport class JSONTreeViewer extends BaseViewer {\n    render() {\n        const container = document.createElement('pre');\n        container.textContent = JSON.stringify(this.sectionData, null, 2);\n        return container;\n    }\n}",
+    "src/components/accordionComponent.js": "export function createAccordion(title, contentElement) {\n    const container = document.createElement('div');\n    container.classList.add('accordion');\n\n    const header = document.createElement('button');\n    header.textContent = title;\n    header.classList.add('accordion-header');\n\n    const content = document.createElement('div');\n    content.classList.add('accordion-content');\n    content.appendChild(contentElement);\n\n    header.addEventListener('click', () => {\n        content.classList.toggle('open');\n    });\n\n    container.appendChild(header);\n    container.appendChild(content);\n    return container;\n}\n",
+    "src/components/tabComponent.js": "export function createTabs(tabsData) {\n    const container = document.createElement('div');\n    container.classList.add('tabs-container');\n\n    const tabs = document.createElement('div');\n    tabs.classList.add('tabs');\n\n    const contentContainer = document.createElement('div');\n    contentContainer.classList.add('tab-content');\n\n    tabsData.forEach((tab, index) => {\n        const button = document.createElement('button');\n        button.textContent = tab.title;\n        button.addEventListener('click', () => {\n            contentContainer.innerHTML = '';\n            contentContainer.appendChild(tab.content);\n        });\n\n        tabs.appendChild(button);\n\n        if (index === 0) {\n            contentContainer.appendChild(tab.content);\n        }\n    });\n\n    container.appendChild(tabs);\n    container.appendChild(contentContainer);\n    return container;\n}\n",
+    "src/ui/languageSelector.js": "import { globalOptions } from '../globalOptions.js';\n\nconst languageSelect = document.getElementById('language-select');\nlanguageSelect.addEventListener('change', (event) => {\n    globalOptions.preferredLanguage = event.target.value;\n});",
+    "src/ui/themeSwitcher.js": "const themeToggle = document.getElementById('theme-toggle');\nthemeToggle.addEventListener('click', () => {\n    document.body.classList.toggle('dark-mode');\n});",
+    "src/ui/adminRenderer.js": "import { createAccordion } from '../components/accordionComponent.js';\nimport { JSONTreeViewer } from './jsonTreeViewer.js';\n\nexport function renderAdministrativeData(adminData) {\n    const container = document.getElementById('admin-data-container');\n    container.innerHTML = ''; // Clear previous content\n\n    if (!adminData) {\n        container.innerHTML = \"<p>No Administrative Data Found</p>\";\n        return;\n    }\n\n    const jsonViewer = new JSONTreeViewer(adminData);\n    const accordion = createAccordion(\"Administrative Data\", jsonViewer.render());\n\n    container.appendChild(accordion);\n}\n",
+    "src/ui/measurementRenderer.js": "import { createTabs } from '../components/tabComponent.js';\nimport { JSONTreeViewer } from './jsonTreeViewer.js';\n\nexport function renderMeasurementResults(measurements) {\n    const container = document.getElementById('measurement-data-container');\n    container.innerHTML = ''; // Clear previous content\n\n    if (!measurements || !measurements.measurementResult) {\n        container.innerHTML = \"<p>No Measurement Results Found</p>\";\n        return;\n    }\n\n    const tabs = measurements.measurementResult.map((result, index) => ({\n        title: `Result ${index + 1}`,\n        content: new JSONTreeViewer(result).render()\n    }));\n\n    const tabsComponent = createTabs(tabs);\n    container.appendChild(tabsComponent);\n}\n",
+    "src/ui/jsonTreeViewer.js": "export class JSONTreeViewer {\n    constructor(data) {\n        this.data = data;\n    }\n\n    render() {\n        const container = document.createElement('pre');\n        container.textContent = JSON.stringify(this.data, null, 2);\n        return container;\n    }\n}\n",
+    "public/index.html": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>DCC Viewer</title>\n    <script type=\"module\" src=\"/src/main.js\"></script>\n    <link rel=\"stylesheet\" href=\"/public/styles.css\">\n</head>\n<body>\n<h1>DCC Viewer</h1>\n\n<input type=\"file\" id=\"file-input\" accept=\".xml\">\n\n<div id=\"admin-data-container\"></div>\n<div id=\"measurement-data-container\"></div>\n</body>\n</html>\n",
+    "public/styles.css": ".accordion-header {\n    background-color: #f0f0f0;\n    border: none;\n    padding: 10px;\n    cursor: pointer;\n    width: 100%;\n    text-align: left;\n}\n\n.accordion-content {\n    display: none;\n    padding: 10px;\n}\n\n.accordion-content.open {\n    display: block;\n}\n\n.tabs {\n    display: flex;\n    gap: 10px;\n}\n\n.tabs button {\n    padding: 5px 10px;\n    cursor: pointer;\n}\n\n.tab-content {\n    border: 1px solid #ddd;\n    padding: 10px;\n}\n"
+}
\ No newline at end of file
diff --git a/project_snapshotter.py b/project_snapshotter.py
new file mode 100644
index 0000000..f8d52ce
--- /dev/null
+++ b/project_snapshotter.py
@@ -0,0 +1,40 @@
+import os
+import json
+
+# Define the root folder of your project
+PROJECT_ROOT = "dcc-viewer"
+
+# File extensions to include
+INCLUDE_EXTENSIONS = {".js", ".html", ".css"}
+
+# Folders to exclude
+EXCLUDE_FOLDERS = {"node_modules", "dist", ".git", ".idea", ".vite"}
+
+def scan_project(directory):
+    project_data = {}
+
+    for root, dirs, files in os.walk(directory):
+        # Remove excluded folders from traversal
+        dirs[:] = [d for d in dirs if d not in EXCLUDE_FOLDERS]
+
+        for file in files:
+            if any(file.endswith(ext) for ext in INCLUDE_EXTENSIONS):
+                file_path = os.path.join(root, file)
+                relative_path = os.path.relpath(file_path, directory)
+
+                try:
+                    with open(file_path, "r", encoding="utf-8") as f:
+                        project_data[relative_path] = f.read()
+                except Exception as e:
+                    print(f"Error reading {file_path}: {e}")
+
+    return project_data
+
+# Run the scan and save as JSON
+if __name__ == "__main__":
+    project_json = scan_project(PROJECT_ROOT)
+
+    with open("project_snapshot.json", "w", encoding="utf-8") as json_file:
+        json.dump(project_json, json_file, indent=4, ensure_ascii=False)
+
+    print("✅ Project snapshot saved to project_snapshot.json")
-- 
GitLab