diff --git a/.gitignore b/.gitignore
index e6ca6341c84d18fd700bba91eb64a386934f2bad..3b8a5a15a7b1fb8d09e7e105c80f6670ace1c6b5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
 out/
+json/
 
 # ide files
 .project
diff --git a/package.json b/package.json
index 8f2e7d0712221a42ac81f1bdc4546ce1b9f35c19..0612fc94267b3bfe5bcfaa5837555b3f0191a68b 100644
--- a/package.json
+++ b/package.json
@@ -23,6 +23,8 @@
     "github-slugger": "^1.2.1",
     "i18n": "^0.8.3",
     "jasmine-xml-reporter": "^1.2.1",
+    "jquery": "^3.7.0",
+    "jsdom": "^22.1.0",
     "json-pointer": "^0.6.0",
     "json-schema-faker": "git+https://git@dev.iopsys.eu/fork/json-schema-faker#iopsys",
     "lodash": "^4.17.15",
diff --git a/uci-to-table.js b/uci-to-table.js
new file mode 100755
index 0000000000000000000000000000000000000000..3233ebc5bd0d7b3e281e38cf7ba53f7f0bf19fad
--- /dev/null
+++ b/uci-to-table.js
@@ -0,0 +1,198 @@
+#! /usr/bin/env node
+
+/* eslint-disable */
+const jsdom = require('jsdom');
+const { JSDOM } = jsdom;
+const Optimist = require('optimist');
+const path = require('path');
+const readdirp = require('readdirp');
+const fs = require('fs');
+const { argv } = Optimist
+  .usage('Generate html table documentation from JSON.\n\nUsage: $0')
+  .demand('d')
+  .alias('d', 'dir')
+  .describe('d', 'path to directory containing the JSON files.')
+  .alias('o', 'output')
+  .demand('o')
+  .describe('o', 'path to an output directory.');
+
+const dom = new JSDOM('<!DOCTYPE html>');
+const $ = (require('jquery'))(dom.window);
+
+const { document } = dom.window;
+
+function isEven(a) {
+  return a % 2 === 0;
+}
+
+function encodeText(a) {
+  return $('<div />').text(a).html();
+}
+
+function isArray(a) {
+  return Object.prototype.toString.call(a) === '[object Array]';
+}
+
+function buildTable(a) {
+  const e = document.createElement('table');
+  let d;
+  let b;
+  if (isArray(a)) return buildArray(a);
+  if (a && typeof(a) === 'object') {
+    Object.keys(a).forEach((c) => {
+      if (typeof a[c] !== 'object' || isArray(a[c])) {
+        if (typeof a[c] === 'object' && isArray(a[c])) {
+          d = e.insertRow(-1);
+          b = d.insertCell(-1);
+          b.colSpan = 2;
+          b.innerHTML = `<div style="font-weight: bold">${encodeText(c)}</div><table style="width:100%">${$(buildArray(a[c]), !1).html()}</table>`;
+        } else {
+          d = e.insertRow(-1);
+          b = d.insertCell(-1);
+          b.innerHTML = `<div style="font-weight: bold">${encodeText(c)}</div>`;
+          d = d.insertCell(-1);
+          d.innerHTML = `<div class='td_row_even'>${encodeText(a[c])}</div>`;
+        }
+      } else {
+        d = e.insertRow(-1);
+        b = d.insertCell(-1);
+        b.colSpan = 2;
+        b.innerHTML = `<div style="font-weight: bold">${encodeText(c)}</div><table style="width:100%">${$(buildTable(a[c]), !1).html()}</table>`;
+      }
+    });
+  }
+
+  return e;
+}
+
+function buildArray(a) {
+  const e = document.createElement('table');
+  let d;
+  let b;
+  let c = !1;
+  let p = !1;
+  const m = {};
+  let h = -1;
+  let n = 0;
+  let l;
+
+  l = '';
+  if (a.length === 0) return '<div></div>';
+  d = e.insertRow(-1);
+  for (let f = 0; f < a.length; f += 1) {
+    if (typeof a[f] !== 'object' || isArray(a[f])) {
+      if (typeof a[f] === 'object' && isArray(a[f])) {
+        b = d.insertCell(h);
+        b.colSpan = 2;
+        b.innerHTML = `<div></div><table style="width:100%">${$(buildArray(a[f]), !1).html()}</table>`;
+        c = !0;
+       } else {
+        if (!p) {
+          h += 1;
+          p = !0;
+          b = d.insertCell(h);
+          m.empty = h;
+          b.innerHTML = '<div>&nbsp;</div>';
+        }
+       }
+    } else {
+      Object.keys(a[f]).forEach((k) => {
+        l = `-${k}`, l in m || (c = !0, h += 1, b = d.insertCell(h), m[l] = h, b.innerHTML = `<div style="font-weight: bold; font-size: 14px">${encodeText(k)}</div>`);
+      });
+    }
+  }
+  c || e.deleteRow(0);
+  n = h + 1;
+  for (let f = 0; f < a.length; f += 1) {
+    let tdClass;
+
+
+    if (d = e.insertRow(-1), tdClass = isEven(f) ? 'td_row_even' : 'td_row_odd', typeof a[f] !== 'object' || isArray(a[f])) {
+      if (typeof a[f] === 'object' && isArray(a[f])) {
+        for (h = m.empty, c = 0; c < n; c += 1) {
+          b = d.insertCell(c), b.className = tdClass, l = c === h ? `<table style="width:100%">${$(buildArray(a[f]), !1).html()}</table>` : ' ', b.innerHTML = `<div class='${tdClass}'>${encodeText(l)
+          }</div>`;
+        }
+      } else
+        for (h = m.empty, c = 0; c < n; c += 1) b = d.insertCell(c), l = c === h ? a[f] : ' ', b.className = tdClass, b.innerHTML = `<div class='${tdClass}'>${encodeText(l)}</div>`;
+    } else {
+      for (c = 0; c < n; c += 1) b = d.insertCell(c), b.className = tdClass, b.innerHTML = `<div class='${tdClass}'>&nbsp;</div>`;
+      for (const k in a[f]) { c = a[f], l = `-${k}`, h = m[l], b = d.cells[h], b.className = tdClass, typeof c[k] !== 'object' || isArray(c[k]) ? typeof c[k] === 'object' && isArray(c[k]) ? b.innerHTML = `<table style="width:100%">${$(buildArray(c[k]), !1).html()}</table>` : b.innerHTML = `<div class='${tdClass}'>${encodeText(c[k])}</div>` : b.innerHTML = `<table style="width:100%">${$(buildTable(c[k]), !1).html()}</table>`; }
+    }
+  }
+  return e;
+}
+
+function processJson(elem) {
+  return buildTable(elem).innerHTML;
+}
+
+const jsonPath = path.resolve(argv.d);
+const target = fs.statSync(jsonPath);
+const files = [];
+const outDir = path.resolve(argv.o);
+
+function parseJson(json) {
+  Object.keys(json).forEach((k) => {
+    let outer = json[k];
+
+    outer.forEach((elem) => {
+      elem.options.forEach((e) => {
+        if (!e.hasOwnProperty("type"))
+          return;
+
+        if (!e.hasOwnProperty("required"))
+          e.required = "no"
+        if (!e.hasOwnProperty("default") || e.default === null ) {
+          switch (e.type) {
+            case 'boolean':
+              e.default = false;
+              break;
+            case 'integer':
+              e.default = 0;
+              break;
+            default:
+              e.default = "null"
+              break;
+          }
+        }
+      })
+    })
+  })
+
+  return json;
+}
+
+function jsonToTable(json, file) {
+  json = parseJson(json);
+  const table = processJson(json);
+  let output = '';
+
+  if (argv.o) output += `${argv.o}/`;
+    output += `${path.basename(file).split('.').slice(0, -1).join('.')}.md`;
+  fs.writeFileSync(output, table);
+}
+
+if (!fs.existsSync(outDir)) {
+  fs.mkdirSync(outDir);
+}
+
+const outTarget = fs.statSync(outDir);
+
+if (outTarget.isDirectory()) {
+  if (target.isDirectory()) {
+    readdirp(jsonPath, { root: jsonPath, fileFilter: '*.json' })
+      .on('data', entry => files.push(entry))
+      .on('end', () => {
+        files.forEach((f) => {
+          const data = JSON.parse(fs.readFileSync(f.fullPath, 'utf8'));
+          const output = `${f.basename.split('.').slice(0, -1).join('.')}.md`;
+
+          jsonToTable(data, output);
+        });
+      });
+  } else {
+    const data = JSON.parse(fs.readFileSync(jsonPath, 'utf8'));
+    jsonToTable(data, jsonPath);
+  }
+} else console.log(`${outDir} is not a directory.`);