Skip to content
Snippets Groups Projects
Commit 938f83fa authored by Benedikt's avatar Benedikt
Browse files

new JS codebase

parent 7300dcd6
No related branches found
No related tags found
No related merge requests found
Showing
with 24 additions and 542 deletions
dcc-viewer/node_modules/
node_modules/
# **📜 Project Outline: Digital Calibration Certificate (DCC) Viewer**
🚀 **Goal:** Develop a fully **client-side** TypeScript application for visualizing, analyzing, and interacting with **Digital Calibration Certificates (DCC)**.
**Features:**
- **Load & Parse DCC XML** (`xml2js`)
- **Extract Key Data** (`administrativeData`, `measurementResults`, etc.)
- **Cross-Reference `id/refID`** (`idRegistry.ts`)
- **Multi-Language Support** (`lang="XX"`)
- **Interactive Plots & Tables** (`Plotly.js`, `DataTables.js`)
---
## **📌 Features & Objectives**
### **1️⃣ XML Parsing & Data Handling**
- 📂 **Load XML files** using `<input type="file">`.
- 🔄 **Convert XML → JSON** (`xml2js`).
- 🔗 **Store elements with `id` in `idRegistry.ts`** for easy lookup.
- 🏷️ **Resolve `refID` references dynamically** across components.
### **2️⃣ UI Rendering & Components**
- 📊 **Data visualization with Plotly.js** (e.g., charge transfer coefficients, uncertainty).
- 📋 **Tabular representation with DataTables.js**.
- 📜 **Expandable/collapsible sections** for hierarchical data.
- 🌍 **Localization support** (show only preferred language).
- 🎨 **Dark mode/light mode toggle**.
### **3️⃣ Interactive Features**
- 📁 **Drag & Drop XML Upload**.
- 🔍 **Search & Filter DCC elements**.
- 🖱️ **Click to view referenced elements (`refID`)**.
- 📤 **Export filtered tables to CSV/JSON**.
---
## **📂 Folder Structure**
```
dcc-viewer/
│── src/ # Source code
│ ├── main.ts # Main entry point
│ ├── xmlToJson.ts # Converts XML → JSON
│ ├── idRegistry.ts # Stores global IDs for cross-referencing
│ ├── globalData.ts # Stores parsed XML data
│ ├── globalOptions.ts # User preferences (language, theme, etc.)
│ ├── viewerRegistry.ts # Maps XML elements to viewer components
│ ├── utils.ts # Helper functions (language filtering, ID lookup)
│ ├── components/ # UI Components
│ │ ├── BaseViewer.ts # Abstract class for all viewers
│ │ ├── AdministrativeDataViewer.ts # View DCC metadata
│ │ ├── MeasurementResultsViewer.ts # View measurement results
│ │ ├── TableViewer.ts # Displays tables for tabular data
│ │ ├── PlotViewer.ts # Uses Plotly.js for interactive plots
│ │ ├── IdentificationViewer.ts # Shows ID-based data
│ │ ├── EquipmentViewer.ts # Displays measuring equipment
│ │ ├── InfluenceConditionsViewer.ts # Shows environmental conditions
│ │ ├── StatementsViewer.ts # Displays decision rules & statements
│ │ ├── ReferenceViewer.ts # Displays cross-referenced elements
│ ├── ui/ # UI-related code
│ │ ├── languageSelector.ts # Dropdown to change language
│ │ ├── themeSwitcher.ts # Dark mode toggle
│ ├── plots/ # Plotly-based visualization components
│── public/ # Static assets
│ ├── index.html # Main UI
│ ├── styles.css # Styling
│── package.json # Dependencies
│── tsconfig.json # TypeScript config
│── README.md # Documentation
```
---
## **📜 `README.md` (Project Documentation)**
```md
# 🏗️ Digital Calibration Certificate (DCC) Viewer
A fully client-side **TypeScript application** for visualizing and analyzing **Digital Calibration Certificates (DCC)**.
---
## ✨ Features
- 📂 **Load DCC XML files** and parse into structured JSON.
- 🔗 **ID & refID-based cross-referencing** for linked elements.
- 🌍 **Localization support** (Choose preferred language for display).
- 📊 **Interactive Plots** using [Plotly.js](https://plotly.com/javascript/).
- 📋 **Sortable, Filterable Tables** using [DataTables.js](https://datatables.net/).
- 🎨 **Dark Mode / Light Mode** UI.
- 🔍 **Search & Filtering** for DCC elements.
- 📤 **Export Options** (Download tables as CSV/JSON).
---
## 📦 Installation & Setup
### **1️⃣ Install Dependencies**
```sh
npm install
```
### **2️⃣ Run Locally**
```sh
npx vite
```
or serve via a local HTTP server:
```sh
npx http-server public
```
---
## 📚 Project Structure
```
dcc-viewer/
│── src/
│ ├── main.ts # Main app logic
│ ├── xmlToJson.ts # Converts XML → JSON
│ ├── idRegistry.ts # Global storage for `id` elements
│ ├── globalOptions.ts # Stores localization & UI preferences
│ ├── viewerRegistry.ts # Registers custom viewers
│ ├── utils.ts # Helper functions (language filtering, ID lookup)
│ ├── components/ # UI Components for rendering DCC data
│ │ ├── BaseViewer.ts
│ │ ├── AdministrativeDataViewer.ts
│ │ ├── MeasurementResultsViewer.ts
│ │ ├── TableViewer.ts
│ │ ├── PlotViewer.ts
│ │ ├── IdentificationViewer.ts
│ │ ├── EquipmentViewer.ts
│ │ ├── InfluenceConditionsViewer.ts
│ │ ├── StatementsViewer.ts
│ │ ├── ReferenceViewer.ts
│── public/
│ ├── index.html # UI entry point
│ ├── styles.css # Basic styles
│── package.json
│── tsconfig.json
```
---
## 🚀 Usage
1. Open **`index.html`** in a browser.
2. Click **"Upload XML"** to load a DCC file.
3. Explore **linked elements**, **localized texts**, and **visualizations**.
---
## 🛠 Technologies Used
- **TypeScript** (Type Safety)
- **xml2js** (XML → JSON Conversion)
- **Plotly.js** (Interactive Plots)
- **DataTables.js** (Filterable Tables)
- **Vite** (Development Server)
- **CSS/HTML** (Basic UI Styling)
---
## 📊 Interactive Features
| Feature | Description |
|---------|------------|
| 📂 XML Parsing | Load DCC, store it in global state |
| 🌍 Localization | Show only elements in preferred language |
| 🔗 ID Linking | Resolve `refID` dynamically |
| 📊 Plots | Render line charts, histograms using Plotly |
| 📋 Tables | Sort, filter, paginate data using DataTables |
| 🎨 Dark Mode | Switch between light/dark themes |
---
## 🛠 Future Improvements
- ✅ **Additional Graph Types**
- ✅ **Export Graphs as Images**
- ✅ **Drag & Drop XML Upload**
- ✅ **LocalStorage for Preferences**
---
## 📜 License
MIT License
```
---
## **🚀 Next Steps**
1. **Implement File Upload UI** (`index.html`, `main.ts`).
2. **Integrate `xml2js` & Build `idRegistry.ts`**.
3. **Implement Measurement & Equipment Viewers** (`MeasurementResultsViewer.ts`, `EquipmentViewer.ts`).
4. **Add Table & Plot Components** (`TableViewer.ts`, `PlotViewer.ts`).
5. **Add Global Language Toggle & Theme Switcher**.
---
### **💡 Does this refactored structure match your vision?** 😃
\ No newline at end of file
......@@ -5,17 +5,27 @@
</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" />
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/package-lock.json" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/package.json" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/public/index.html" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/public/styles.css" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/src/components/BaseViewer.js" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/src/components/JSONTreeViewer.js" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/src/components/accordionComponent.js" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/src/components/tabComponent.js" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/src/globalData.js" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/src/globalOptions.js" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/src/idRegistry.js" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/src/main.js" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/src/ui/adminRenderer.js" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/src/ui/jsonTreeViewer.js" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/src/ui/languageSelector.js" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/src/ui/measurementRenderer.js" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/src/ui/themeSwitcher.js" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/src/utils.js" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/src/viewerRegistry.js" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/src/xmlToJson.js" beforeDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
......@@ -85,7 +95,7 @@
<workItem from="1740132146315" duration="24000" />
<workItem from="1740132175271" duration="89000" />
<workItem from="1740158794192" duration="1155000" />
<workItem from="1740380748268" duration="2676000" />
<workItem from="1740380748268" duration="5118000" />
</task>
<servers />
</component>
......
{
"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"
}
}
}
}
{
"name": "dcc-viewer",
"version": "1.0.0",
"scripts": {
"dev": "vite"
},
"dependencies": {
"xml-js": "^1.6.11"
}
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>DCC Viewer</title>
<script type="module" src="/src/main.js"></script>
<link rel="stylesheet" href="/public/styles.css">
</head>
<body>
<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>
.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;
}
export class BaseViewer {
constructor(sectionData) {
this.sectionData = sectionData;
}
render() {
throw new Error('render() must be implemented in subclasses');
}
}
\ No newline at end of file
import { BaseViewer } from './BaseViewer.js';
export class JSONTreeViewer extends BaseViewer {
render() {
const container = document.createElement('pre');
container.textContent = JSON.stringify(this.sectionData, null, 2);
return container;
}
}
\ No newline at end of file
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;
}
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;
}
export const globalData = {
dcc: null // This will hold the parsed DCC JSON data
};
\ No newline at end of file
export const globalOptions = { preferredLanguage: 'en' };
\ No newline at end of file
export const idRegistry = {};
\ No newline at end of file
import { convertXMLToJSON } from './xmlToJson.js';
import { globalData } from './globalData.js';
import { renderAdministrativeData } from './ui/adminRenderer.js';
import { renderMeasurementResults } from './ui/measurementRenderer.js';
const fileInput = document.getElementById('file-input');
fileInput.addEventListener('change', async (event) => {
const file = event.target.files?.[0];
if (!file) return;
const reader = new FileReader();
reader.onload = async () => {
const xmlString = reader.result;
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);
});
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()));
}
export class JSONTreeViewer {
constructor(data) {
this.data = data;
}
render() {
const container = document.createElement('pre');
container.textContent = JSON.stringify(this.data, null, 2);
return container;
}
}
import { globalOptions } from '../globalOptions.js';
const languageSelect = document.getElementById('language-select');
languageSelect.addEventListener('change', (event) => {
globalOptions.preferredLanguage = event.target.value;
});
\ No newline at end of file
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() }]));
}
const themeToggle = document.getElementById('theme-toggle');
themeToggle.addEventListener('click', () => {
document.body.classList.toggle('dark-mode');
});
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment