@peter.prib/renderengine 0.15.2 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/renderengine-workspace.umd.js +6030 -3766
- package/dist/renderengine.umd.js +5930 -3772
- package/documentation/REFERENCE.md +37 -3
- package/package.json +1 -1
- package/src/archimateArchiFormat.js +114 -10
- package/src/archimateArchive.js +27 -4
- package/src/archimateExchangeFormat.js +12 -4
- package/src/archimateSpecializations.js +11 -1
- package/src/chartCatalog.js +6 -4
- package/src/charting/ArchimateDiagramEditor.js +5 -108
- package/src/charting/DiagramTypeRegistry.js +20 -1
- package/src/charting/DiagramViewPropertiesDialog.js +54 -10
- package/src/charting/Legend.js +1 -2
- package/src/charting/Statistics.js +14 -0
- package/src/charting/charts/BarChart.js +59 -8
- package/src/charting/charts/HexbinChart.js +15 -0
- package/src/charting/charts/LollipopChart.js +42 -7
- package/src/charting/decision/DecisionWorkbench.js +137 -4
- package/src/charting/diagramModel/DiagramView.js +7 -2
- package/src/charting/diagrams/GraphDiagram3D.js +15 -0
- package/src/charting/graph/GraphSource.js +72 -0
- package/src/charting/modelTree/NavigatorModel.js +22 -8
- package/src/charting/report/TemplatedReport.js +27 -377
- package/src/charting/report/ViewsReport.js +118 -0
- package/src/charting/report/reportWriters.js +496 -0
- package/src/charting/templates/C4BoundaryTemplate.js +34 -0
- package/src/charting/templates/C4ComponentTemplate.js +30 -0
- package/src/charting/templates/C4ContainerTemplate.js +49 -0
- package/src/charting/templates/C4PersonTemplate.js +35 -0
- package/src/charting/templates/C4SystemTemplate.js +32 -0
- package/src/charting/templates/C4Template.js +95 -0
- package/src/charting/templates/index.js +6 -0
- package/src/charting/templates/templateRegistry.js +7 -0
- package/src/compute/ComputeBackend.js +132 -0
- package/src/compute/CpuBackend.js +209 -0
- package/src/compute/WebGpuBackend.js +630 -0
- package/src/compute/index.js +5 -0
- package/src/compute/selectBackend.js +85 -0
- package/src/index.js +3403 -3371
- package/src/kernelExpr.js +80 -5
- package/src/workspace/parts/DiagramEditorPart.js +3 -1
- package/src/workspace/parts/ViewsPart.js +106 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GraphSource — what a graph walker reads, whatever the graph is.
|
|
3
|
+
*
|
|
4
|
+
* `NavigatorModel` (step-by-step exploration, `findPaths`, `traceImpact`) asks a graph four questions:
|
|
5
|
+
*
|
|
6
|
+
* - `getElement(id)` — the element, `{ id, name, type, ... }`, or null;
|
|
7
|
+
* - `relationshipsFor(id)` — `{ incoming, outgoing }`, each relationship `{ id, source, target, kind }`;
|
|
8
|
+
* - `viewsContaining(id)` — the views that draw it (none, for a graph that has no views);
|
|
9
|
+
* - `layerOf(element)` — the band it belongs to, for grouping and colour.
|
|
10
|
+
*
|
|
11
|
+
* Why an interface. Traversal is the same for any graph; only these reads differ. So there is one walker,
|
|
12
|
+
* and each kind of graph answers the four questions: `ArchimateModel` answers them already (its layers
|
|
13
|
+
* from `treeLayerOf`), and `DocumentGraph` answers them for a graph document — `{ nodes, edges }`, the
|
|
14
|
+
* shape sqlengine stores graphs in and decisionengine scores — so a portfolio, a dependency graph or any
|
|
15
|
+
* stored graph is explored the way an ArchiMate model is, without a second walker.
|
|
16
|
+
*
|
|
17
|
+
* A subclass implements the four reads; this base says what is missing rather than failing obscurely.
|
|
18
|
+
*/
|
|
19
|
+
export class GraphSource {
|
|
20
|
+
getElement() { throw new Error(`${this.constructor.name} must implement getElement(id)`); }
|
|
21
|
+
relationshipsFor() { throw new Error(`${this.constructor.name} must implement relationshipsFor(id)`); }
|
|
22
|
+
viewsContaining() { return []; }
|
|
23
|
+
layerOf(element) { return element?.type ?? 'other'; }
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* DocumentGraph — a graph document (`{ nodes: [{ id, labels, props }], edges: [{ id?, from, to, type, props }] }`)
|
|
28
|
+
* as a GraphSource. A node's type is its first label and its name `props.name` (else its id); an edge's
|
|
29
|
+
* kind is its `type`, its id its own or one made from its ends. Its layer is its type, so a portfolio's
|
|
30
|
+
* capabilities, epics and objectives band apart. Indexed once, read many.
|
|
31
|
+
*/
|
|
32
|
+
export class DocumentGraph extends GraphSource {
|
|
33
|
+
/** True for something shaped like a graph document. */
|
|
34
|
+
static isDocument(doc) {
|
|
35
|
+
return Boolean(doc) && Array.isArray(doc.nodes) && (doc.edges === undefined || Array.isArray(doc.edges));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
constructor(doc) {
|
|
39
|
+
super();
|
|
40
|
+
if (!DocumentGraph.isDocument(doc)) throw new Error('A graph document has a nodes list (and an edges list)');
|
|
41
|
+
this.elements = new Map();
|
|
42
|
+
for (const node of doc.nodes) {
|
|
43
|
+
if (node?.id == null) continue;
|
|
44
|
+
this.elements.set(node.id, Object.freeze({
|
|
45
|
+
id: node.id, name: node.props?.name ?? String(node.id), type: node.labels?.[0] ?? 'Node',
|
|
46
|
+
labels: node.labels ?? [], props: node.props ?? {},
|
|
47
|
+
}));
|
|
48
|
+
}
|
|
49
|
+
this.incoming = new Map();
|
|
50
|
+
this.outgoing = new Map();
|
|
51
|
+
(doc.edges ?? []).forEach((edge, i) => {
|
|
52
|
+
if (!this.elements.has(edge?.from) || !this.elements.has(edge?.to)) return;
|
|
53
|
+
const relationship = Object.freeze({
|
|
54
|
+
id: edge.id ?? `${edge.from}-${edge.type}-${edge.to}-${i}`, source: edge.from, target: edge.to,
|
|
55
|
+
kind: edge.type, name: edge.props?.name ?? edge.type, props: edge.props ?? {},
|
|
56
|
+
});
|
|
57
|
+
push(this.outgoing, edge.from, relationship);
|
|
58
|
+
push(this.incoming, edge.to, relationship);
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
getElement(id) { return this.elements.get(id) ?? null; }
|
|
63
|
+
|
|
64
|
+
relationshipsFor(id) {
|
|
65
|
+
return { incoming: this.incoming.get(id) ?? [], outgoing: this.outgoing.get(id) ?? [] };
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function push(map, key, value) {
|
|
70
|
+
if (!map.has(key)) map.set(key, []);
|
|
71
|
+
map.get(key).push(value);
|
|
72
|
+
}
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { treeLayerOf } from './ModelTreePanel.js';
|
|
2
|
+
import { DocumentGraph } from '../graph/GraphSource.js';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
|
-
* NavigatorModel — pure, DOM-free graph traversal and navigation engine for an ArchiMate
|
|
5
|
+
* NavigatorModel — pure, DOM-free graph traversal and navigation engine for any graph: an ArchiMate
|
|
6
|
+
* model, or any GraphSource (`DocumentGraph` for a `{ nodes, edges }` graph document — see
|
|
7
|
+
* `NavigatorModel.forGraph`). It reads the graph only through `getElement`, `relationshipsFor`,
|
|
8
|
+
* `viewsContaining` and `layerOf` (see `GraphSource` for why).
|
|
5
9
|
*
|
|
6
10
|
* Allows exploring relationships step-by-step from any focused root element to arbitrary depth,
|
|
7
11
|
* with cycle detection, navigation history (back/forward), direction filters, path-finding
|
|
@@ -9,12 +13,17 @@ import { treeLayerOf } from './ModelTreePanel.js';
|
|
|
9
13
|
*/
|
|
10
14
|
export class NavigatorModel {
|
|
11
15
|
/**
|
|
12
|
-
* @param {ArchimateModel} model
|
|
16
|
+
* @param {ArchimateModel|import('../graph/GraphSource.js').GraphSource} model
|
|
13
17
|
* @param {Object} [options]
|
|
14
18
|
* @param {string} [options.rootId] - Initial focused element id
|
|
15
19
|
* @param {'both'|'outgoing'|'incoming'} [options.direction='both']
|
|
16
20
|
* @param {Iterable<string>} [options.allowedKinds] - Optional relationship kinds to include
|
|
17
21
|
*/
|
|
22
|
+
/** A navigator over a graph document (`{ nodes, edges }`): the same walker, through a DocumentGraph. */
|
|
23
|
+
static forGraph(doc, options) {
|
|
24
|
+
return new NavigatorModel(new DocumentGraph(doc), options);
|
|
25
|
+
}
|
|
26
|
+
|
|
18
27
|
constructor(model, { rootId = null, direction = 'both', allowedKinds = null } = {}) {
|
|
19
28
|
this.model = model;
|
|
20
29
|
this.direction = direction;
|
|
@@ -39,6 +48,11 @@ export class NavigatorModel {
|
|
|
39
48
|
return () => this._listeners.delete(listener);
|
|
40
49
|
}
|
|
41
50
|
|
|
51
|
+
/** An element's layer: the graph's own answer, or ArchiMate's (an ArchimateModel has none of its own). */
|
|
52
|
+
layerOf(element) {
|
|
53
|
+
return typeof this.model.layerOf === 'function' ? this.model.layerOf(element) : treeLayerOf(element);
|
|
54
|
+
}
|
|
55
|
+
|
|
42
56
|
_notify() {
|
|
43
57
|
for (const listener of this._listeners) {
|
|
44
58
|
try {
|
|
@@ -209,7 +223,7 @@ export class NavigatorModel {
|
|
|
209
223
|
direction: dir,
|
|
210
224
|
relationship: rel,
|
|
211
225
|
element: otherElement,
|
|
212
|
-
layer:
|
|
226
|
+
layer: this.layerOf(otherElement),
|
|
213
227
|
cycle,
|
|
214
228
|
expanded,
|
|
215
229
|
children,
|
|
@@ -227,8 +241,8 @@ export class NavigatorModel {
|
|
|
227
241
|
return {
|
|
228
242
|
pathKey,
|
|
229
243
|
element,
|
|
230
|
-
layer:
|
|
231
|
-
views: this.model.viewsContaining(element.id),
|
|
244
|
+
layer: this.layerOf(element),
|
|
245
|
+
views: typeof this.model.viewsContaining === 'function' ? this.model.viewsContaining(element.id) : [],
|
|
232
246
|
incoming: incomingBranches,
|
|
233
247
|
outgoing: outgoingBranches,
|
|
234
248
|
};
|
|
@@ -355,7 +369,7 @@ export class NavigatorModel {
|
|
|
355
369
|
const depths = new Map([[elementId, 0]]);
|
|
356
370
|
const elementsMap = new Map();
|
|
357
371
|
const relsMap = new Map();
|
|
358
|
-
const layers = new Set([
|
|
372
|
+
const layers = new Set([this.layerOf(root)]);
|
|
359
373
|
|
|
360
374
|
const queue = [{ id: elementId, depth: 0 }];
|
|
361
375
|
|
|
@@ -376,7 +390,7 @@ export class NavigatorModel {
|
|
|
376
390
|
if (!depths.has(target.id)) {
|
|
377
391
|
depths.set(target.id, nextDepth);
|
|
378
392
|
elementsMap.set(target.id, target);
|
|
379
|
-
layers.add(
|
|
393
|
+
layers.add(this.layerOf(target));
|
|
380
394
|
queue.push({ id: target.id, depth: nextDepth });
|
|
381
395
|
}
|
|
382
396
|
}
|
|
@@ -392,7 +406,7 @@ export class NavigatorModel {
|
|
|
392
406
|
if (!depths.has(source.id)) {
|
|
393
407
|
depths.set(source.id, nextDepth);
|
|
394
408
|
elementsMap.set(source.id, source);
|
|
395
|
-
layers.add(
|
|
409
|
+
layers.add(this.layerOf(source));
|
|
396
410
|
queue.push({ id: source.id, depth: nextDepth });
|
|
397
411
|
}
|
|
398
412
|
}
|
|
@@ -1,11 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* TemplatedReport — template-driven report generator (
|
|
2
|
+
* TemplatedReport — template-driven report generator (HTML, Word, Excel, CSV, Markdown).
|
|
3
3
|
*
|
|
4
4
|
* Implements Archi's Jasper-style reporting with customizable templates,
|
|
5
5
|
* placeholders for model metadata, views, view diagrams (SVG/PNG), elements,
|
|
6
6
|
* relationships, and properties.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
+
import {
|
|
10
|
+
escapeHtml,
|
|
11
|
+
escapeXml,
|
|
12
|
+
escapeCsv,
|
|
13
|
+
formatHtml,
|
|
14
|
+
formatWord,
|
|
15
|
+
formatExcel,
|
|
16
|
+
formatCsv,
|
|
17
|
+
formatMarkdown,
|
|
18
|
+
} from './reportWriters.js';
|
|
19
|
+
|
|
20
|
+
export { escapeHtml, escapeXml, escapeCsv };
|
|
21
|
+
|
|
9
22
|
/** Built-in template templates */
|
|
10
23
|
export const REPORT_TEMPLATES = Object.freeze({
|
|
11
24
|
EXECUTIVE: 'executive',
|
|
@@ -13,27 +26,6 @@ export const REPORT_TEMPLATES = Object.freeze({
|
|
|
13
26
|
TABULAR: 'tabular',
|
|
14
27
|
});
|
|
15
28
|
|
|
16
|
-
function escapeHtml(str) {
|
|
17
|
-
return String(str ?? '')
|
|
18
|
-
.replace(/&/g, '&')
|
|
19
|
-
.replace(/</g, '<')
|
|
20
|
-
.replace(/>/g, '>')
|
|
21
|
-
.replace(/"/g, '"')
|
|
22
|
-
.replace(/'/g, ''');
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function escapeXml(str) {
|
|
26
|
-
return escapeHtml(str);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function escapeCsv(val) {
|
|
30
|
-
const s = String(val ?? '');
|
|
31
|
-
if (/[",\n\r]/.test(s)) {
|
|
32
|
-
return `"${s.replace(/"/g, '""')}"`;
|
|
33
|
-
}
|
|
34
|
-
return s;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
29
|
export class TemplatedReport {
|
|
38
30
|
/**
|
|
39
31
|
* @param {Object|ViewSetModel|ArchimateModel} model
|
|
@@ -50,14 +42,14 @@ export class TemplatedReport {
|
|
|
50
42
|
}
|
|
51
43
|
|
|
52
44
|
get title() {
|
|
53
|
-
return this._title || this.model
|
|
45
|
+
return this._title || this.model?.name || this.model?.title || 'Model Report';
|
|
54
46
|
}
|
|
55
47
|
|
|
56
48
|
/**
|
|
57
49
|
* Normalized model data extracted for reporting.
|
|
58
50
|
*/
|
|
59
51
|
extractData() {
|
|
60
|
-
const m = this.model;
|
|
52
|
+
const m = this.model || {};
|
|
61
53
|
const title = this.title;
|
|
62
54
|
const doc = m.documentation || '';
|
|
63
55
|
|
|
@@ -149,6 +141,9 @@ export class TemplatedReport {
|
|
|
149
141
|
version: m.version || '1.0',
|
|
150
142
|
properties,
|
|
151
143
|
},
|
|
144
|
+
title,
|
|
145
|
+
documentation: doc,
|
|
146
|
+
version: m.version || '1.0',
|
|
152
147
|
views,
|
|
153
148
|
elements: normElements,
|
|
154
149
|
relationships: normRelationships,
|
|
@@ -163,279 +158,24 @@ export class TemplatedReport {
|
|
|
163
158
|
* @param {boolean} [options.printable=true]
|
|
164
159
|
* @returns {string}
|
|
165
160
|
*/
|
|
166
|
-
toHtml(
|
|
167
|
-
|
|
168
|
-
const { model, views, elements, relationships, properties, date } = data;
|
|
169
|
-
|
|
170
|
-
const printCss = printable ? `
|
|
171
|
-
@media print {
|
|
172
|
-
body { background: #fff; margin: 0; padding: 0; font-size: 11pt; color: #000; }
|
|
173
|
-
header, footer { position: fixed; width: 100%; left: 0; font-size: 9pt; color: #777; }
|
|
174
|
-
header { top: 0; border-bottom: 1px solid #ccc; padding-bottom: 4px; }
|
|
175
|
-
footer { bottom: 0; border-top: 1px solid #ccc; padding-top: 4px; display: flex; justify-content: space-between; }
|
|
176
|
-
.page-break { page-break-after: always; }
|
|
177
|
-
.no-break { page-break-inside: avoid; }
|
|
178
|
-
.picture { max-width: 100% !important; page-break-inside: avoid; }
|
|
179
|
-
table { page-break-inside: auto; }
|
|
180
|
-
tr { page-break-inside: avoid; page-break-after: auto; }
|
|
181
|
-
}
|
|
182
|
-
@page {
|
|
183
|
-
size: A4;
|
|
184
|
-
margin: 20mm;
|
|
185
|
-
}` : '';
|
|
186
|
-
|
|
187
|
-
return `<!DOCTYPE html>
|
|
188
|
-
<html lang="en">
|
|
189
|
-
<head>
|
|
190
|
-
<meta charset="utf-8">
|
|
191
|
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
192
|
-
<title>${escapeHtml(model.name)}</title>
|
|
193
|
-
<style>
|
|
194
|
-
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; line-height: 1.5; color: #222; margin: 24px auto; max-width: 960px; padding: 0 16px; }
|
|
195
|
-
h1 { font-size: 26px; border-bottom: 2px solid #2563eb; padding-bottom: 6px; margin-top: 24px; color: #1e3a8a; }
|
|
196
|
-
h2 { font-size: 20px; border-bottom: 1px solid #cbd5e1; padding-bottom: 4px; margin-top: 24px; color: #1e40af; }
|
|
197
|
-
h3 { font-size: 16px; margin-top: 18px; color: #334155; }
|
|
198
|
-
.meta { font-size: 13px; color: #64748b; margin-bottom: 20px; }
|
|
199
|
-
.doc { white-space: pre-wrap; background: #f8fafc; border-left: 4px solid #3b82f6; padding: 10px 14px; margin: 12px 0; font-size: 14px; }
|
|
200
|
-
table { width: 100%; border-collapse: collapse; margin: 16px 0; font-size: 13px; }
|
|
201
|
-
th, td { border: 1px solid #e2e8f0; padding: 6px 10px; text-align: left; vertical-align: top; }
|
|
202
|
-
th { background: #f1f5f9; font-weight: 600; color: #334155; }
|
|
203
|
-
tr:nth-child(even) { background: #f8fafc; }
|
|
204
|
-
.picture { margin: 16px 0; border: 1px solid #e2e8f0; border-radius: 4px; padding: 8px; background: #fff; overflow-x: auto; text-align: center; }
|
|
205
|
-
.picture svg { max-width: 100%; height: auto; }
|
|
206
|
-
.badge { display: inline-block; padding: 2px 6px; font-size: 11px; font-weight: 500; border-radius: 3px; background: #e0f2fe; color: #0369a1; }
|
|
207
|
-
${printCss}
|
|
208
|
-
</style>
|
|
209
|
-
</head>
|
|
210
|
-
<body>
|
|
211
|
-
<h1>${escapeHtml(model.name)}</h1>
|
|
212
|
-
<div class="meta">Generated: ${escapeHtml(date)} | Version: ${escapeHtml(model.version)}</div>
|
|
213
|
-
${model.documentation ? `<div class="doc">${escapeHtml(model.documentation)}</div>` : ''}
|
|
214
|
-
|
|
215
|
-
${properties.length ? `
|
|
216
|
-
<h2>Model Properties</h2>
|
|
217
|
-
<table>
|
|
218
|
-
<thead><tr><th>Property</th><th>Value</th></tr></thead>
|
|
219
|
-
<tbody>
|
|
220
|
-
${properties.map(p => `<tr><td>${escapeHtml(p.name)}</td><td>${escapeHtml(p.value)}</td></tr>`).join('')}
|
|
221
|
-
</tbody>
|
|
222
|
-
</table>` : ''}
|
|
223
|
-
|
|
224
|
-
<h2>Views (${views.length})</h2>
|
|
225
|
-
${views.map(v => `
|
|
226
|
-
<div class="no-break">
|
|
227
|
-
<h3>${escapeHtml(v.name)}</h3>
|
|
228
|
-
${v.documentation ? `<div class="doc">${escapeHtml(v.documentation)}</div>` : ''}
|
|
229
|
-
${v.picture ? `<div class="picture">${v.picture}</div>` : ''}
|
|
230
|
-
${v.elements.length ? `
|
|
231
|
-
<table>
|
|
232
|
-
<thead><tr><th>Element</th><th>Type</th></tr></thead>
|
|
233
|
-
<tbody>
|
|
234
|
-
${v.elements.map(e => `<tr><td>${escapeHtml(e.name)}</td><td><span class="badge">${escapeHtml(e.type)}</span></td></tr>`).join('')}
|
|
235
|
-
</tbody>
|
|
236
|
-
</table>` : ''}
|
|
237
|
-
</div>
|
|
238
|
-
<div class="page-break"></div>
|
|
239
|
-
`).join('')}
|
|
240
|
-
|
|
241
|
-
${elements.length ? `
|
|
242
|
-
<h2>Elements (${elements.length})</h2>
|
|
243
|
-
<table>
|
|
244
|
-
<thead><tr><th>Name</th><th>Type</th><th>Layer</th><th>Documentation</th></tr></thead>
|
|
245
|
-
<tbody>
|
|
246
|
-
${elements.map(e => `<tr><td><strong>${escapeHtml(e.name)}</strong></td><td><span class="badge">${escapeHtml(e.type)}</span></td><td>${escapeHtml(e.layer)}</td><td>${escapeHtml(e.documentation)}</td></tr>`).join('')}
|
|
247
|
-
</tbody>
|
|
248
|
-
</table>` : ''}
|
|
249
|
-
|
|
250
|
-
${relationships.length ? `
|
|
251
|
-
<h2>Relationships (${relationships.length})</h2>
|
|
252
|
-
<table>
|
|
253
|
-
<thead><tr><th>Type</th><th>Source</th><th>Target</th><th>Name</th><th>Documentation</th></tr></thead>
|
|
254
|
-
<tbody>
|
|
255
|
-
${relationships.map(r => `<tr><td><span class="badge">${escapeHtml(r.type)}</span></td><td>${escapeHtml(r.source)}</td><td>${escapeHtml(r.target)}</td><td>${escapeHtml(r.name)}</td><td>${escapeHtml(r.documentation)}</td></tr>`).join('')}
|
|
256
|
-
</tbody>
|
|
257
|
-
</table>` : ''}
|
|
258
|
-
|
|
259
|
-
</body>
|
|
260
|
-
</html>`;
|
|
161
|
+
toHtml(options = {}) {
|
|
162
|
+
return formatHtml(this.extractData(), options);
|
|
261
163
|
}
|
|
262
164
|
|
|
263
165
|
/**
|
|
264
|
-
* Render the report as Microsoft Word-compatible
|
|
265
|
-
* Word opens this with native tables, headings, and formatting intact.
|
|
166
|
+
* Render the report as Microsoft Word-compatible document (.doc).
|
|
266
167
|
* @returns {string}
|
|
267
168
|
*/
|
|
268
169
|
toWord() {
|
|
269
|
-
|
|
270
|
-
const { model, views, elements, relationships, properties, date } = data;
|
|
271
|
-
|
|
272
|
-
return `<html xmlns:o="urn:schemas-microsoft-com:office:office"
|
|
273
|
-
xmlns:w="urn:schemas-microsoft-com:office:word"
|
|
274
|
-
xmlns="http://www.w3.org/TR/REC-html40">
|
|
275
|
-
<head>
|
|
276
|
-
<meta charset="utf-8">
|
|
277
|
-
<title>${escapeXml(model.name)}</title>
|
|
278
|
-
<!--[if gte mso 9]>
|
|
279
|
-
<xml>
|
|
280
|
-
<w:WordDocument>
|
|
281
|
-
<w:View>Print</w:View>
|
|
282
|
-
<w:Zoom>100</w:Zoom>
|
|
283
|
-
<w:DoNotOptimizeForBrowser/>
|
|
284
|
-
</w:WordDocument>
|
|
285
|
-
</xml>
|
|
286
|
-
<![endif]-->
|
|
287
|
-
<style>
|
|
288
|
-
body { font-family: Calibri, Arial, sans-serif; font-size: 11pt; line-height: 1.4; color: #333333; }
|
|
289
|
-
h1 { font-size: 22pt; color: #1f4e79; border-bottom: 2pt solid #1f4e79; margin-bottom: 12pt; }
|
|
290
|
-
h2 { font-size: 16pt; color: #2e75b6; margin-top: 18pt; margin-bottom: 8pt; }
|
|
291
|
-
h3 { font-size: 13pt; color: #595959; margin-top: 12pt; }
|
|
292
|
-
p.doc { font-style: italic; background-color: #f2f2f2; padding: 6pt; border-left: 3pt solid #2e75b6; }
|
|
293
|
-
table { width: 100%; border-collapse: collapse; margin-top: 8pt; margin-bottom: 12pt; }
|
|
294
|
-
th, td { border: 0.5pt solid #d9d9d9; padding: 4pt 6pt; font-size: 10pt; }
|
|
295
|
-
th { background-color: #f2f2f2; font-weight: bold; color: #1f4e79; }
|
|
296
|
-
.page-break { page-break-before: always; }
|
|
297
|
-
</style>
|
|
298
|
-
</head>
|
|
299
|
-
<body>
|
|
300
|
-
<h1>${escapeXml(model.name)}</h1>
|
|
301
|
-
<p><strong>Generated:</strong> ${escapeXml(date)} | <strong>Version:</strong> ${escapeXml(model.version)}</p>
|
|
302
|
-
${model.documentation ? `<p class="doc">${escapeXml(model.documentation)}</p>` : ''}
|
|
303
|
-
|
|
304
|
-
${properties.length ? `
|
|
305
|
-
<h2>Properties</h2>
|
|
306
|
-
<table>
|
|
307
|
-
<tr><th>Property</th><th>Value</th></tr>
|
|
308
|
-
${properties.map(p => `<tr><td>${escapeXml(p.name)}</td><td>${escapeXml(p.value)}</td></tr>`).join('')}
|
|
309
|
-
</table>` : ''}
|
|
310
|
-
|
|
311
|
-
<h2>Views</h2>
|
|
312
|
-
${views.map(v => `
|
|
313
|
-
<div class="page-break"></div>
|
|
314
|
-
<h3>${escapeXml(v.name)}</h3>
|
|
315
|
-
${v.documentation ? `<p class="doc">${escapeXml(v.documentation)}</p>` : ''}
|
|
316
|
-
${v.picture ? `<div>${v.picture}</div>` : ''}
|
|
317
|
-
${v.elements.length ? `
|
|
318
|
-
<table>
|
|
319
|
-
<tr><th>Element</th><th>Type</th></tr>
|
|
320
|
-
${v.elements.map(e => `<tr><td>${escapeXml(e.name)}</td><td>${escapeXml(e.type)}</td></tr>`).join('')}
|
|
321
|
-
</table>` : ''}
|
|
322
|
-
`).join('')}
|
|
323
|
-
|
|
324
|
-
${elements.length ? `
|
|
325
|
-
<div class="page-break"></div>
|
|
326
|
-
<h2>Elements (${elements.length})</h2>
|
|
327
|
-
<table>
|
|
328
|
-
<tr><th>Name</th><th>Type</th><th>Layer</th><th>Documentation</th></tr>
|
|
329
|
-
${elements.map(e => `<tr><td><strong>${escapeXml(e.name)}</strong></td><td>${escapeXml(e.type)}</td><td>${escapeXml(e.layer)}</td><td>${escapeXml(e.documentation)}</td></tr>`).join('')}
|
|
330
|
-
</table>` : ''}
|
|
331
|
-
|
|
332
|
-
${relationships.length ? `
|
|
333
|
-
<div class="page-break"></div>
|
|
334
|
-
<h2>Relationships (${relationships.length})</h2>
|
|
335
|
-
<table>
|
|
336
|
-
<tr><th>Type</th><th>Source</th><th>Target</th><th>Name</th></tr>
|
|
337
|
-
${relationships.map(r => `<tr><td>${escapeXml(r.type)}</td><td>${escapeXml(r.source)}</td><td>${escapeXml(r.target)}</td><td>${escapeXml(r.name)}</td></tr>`).join('')}
|
|
338
|
-
</table>` : ''}
|
|
339
|
-
|
|
340
|
-
</body>
|
|
341
|
-
</html>`;
|
|
170
|
+
return formatWord(this.extractData());
|
|
342
171
|
}
|
|
343
172
|
|
|
344
173
|
/**
|
|
345
|
-
* Render the report as Microsoft Excel XML Spreadsheet 2003 format (.xml
|
|
346
|
-
* Opens in Excel with native worksheets: Model, Views, Elements, Relationships, Properties.
|
|
174
|
+
* Render the report as Microsoft Excel XML Spreadsheet 2003 format (.xml).
|
|
347
175
|
* @returns {string}
|
|
348
176
|
*/
|
|
349
177
|
toExcel() {
|
|
350
|
-
|
|
351
|
-
const { model, views, elements, relationships, properties, date } = data;
|
|
352
|
-
|
|
353
|
-
const makeRow = (cells, isHeader = false) => `
|
|
354
|
-
<Row>
|
|
355
|
-
${cells.map(c => ` <Cell${isHeader ? ' ss:StyleID="Header"' : ''}><Data ss:Type="String">${escapeXml(c)}</Data></Cell>`).join('\n')}
|
|
356
|
-
</Row>`;
|
|
357
|
-
|
|
358
|
-
return `<?xml version="1.0"?>
|
|
359
|
-
<?mso-application progid="Excel.Sheet"?>
|
|
360
|
-
<Workbook xmlns="urn:schemas-microsoft-com:office:spreadsheet"
|
|
361
|
-
xmlns:o="urn:schemas-microsoft-com:office:office"
|
|
362
|
-
xmlns:x="urn:schemas-microsoft-com:office:excel"
|
|
363
|
-
xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet">
|
|
364
|
-
<Styles>
|
|
365
|
-
<Style ss:ID="Default" ss:Name="Normal">
|
|
366
|
-
<Alignment ss:Vertical="Center"/>
|
|
367
|
-
<Font ss:FontName="Calibri" ss:Size="11" ss:Color="#000000"/>
|
|
368
|
-
</Style>
|
|
369
|
-
<Style ss:ID="Header">
|
|
370
|
-
<Font ss:FontName="Calibri" ss:Size="11" ss:Color="#FFFFFF" ss:Bold="1"/>
|
|
371
|
-
<Interior ss:Color="#2563EB" ss:Pattern="Solid"/>
|
|
372
|
-
</Style>
|
|
373
|
-
<Style ss:ID="Title">
|
|
374
|
-
<Font ss:FontName="Calibri" ss:Size="16" ss:Color="#1E3A8A" ss:Bold="1"/>
|
|
375
|
-
</Style>
|
|
376
|
-
</Styles>
|
|
377
|
-
|
|
378
|
-
<Worksheet ss:Name="Model Summary">
|
|
379
|
-
<Table>
|
|
380
|
-
<Column ss:Width="160"/>
|
|
381
|
-
<Column ss:Width="400"/>
|
|
382
|
-
<Row>
|
|
383
|
-
<Cell ss:StyleID="Title"><Data ss:Type="String">${escapeXml(model.name)}</Data></Cell>
|
|
384
|
-
</Row>
|
|
385
|
-
${makeRow(['Item', 'Details'], true)}
|
|
386
|
-
${makeRow(['Title', model.name])}
|
|
387
|
-
${makeRow(['Version', model.version])}
|
|
388
|
-
${makeRow(['Generated', date])}
|
|
389
|
-
${makeRow(['Documentation', model.documentation])}
|
|
390
|
-
${makeRow(['Total Views', String(views.length)])}
|
|
391
|
-
${makeRow(['Total Elements', String(elements.length)])}
|
|
392
|
-
${makeRow(['Total Relationships', String(relationships.length)])}
|
|
393
|
-
</Table>
|
|
394
|
-
</Worksheet>
|
|
395
|
-
|
|
396
|
-
<Worksheet ss:Name="Views">
|
|
397
|
-
<Table>
|
|
398
|
-
<Column ss:Width="180"/>
|
|
399
|
-
<Column ss:Width="80"/>
|
|
400
|
-
<Column ss:Width="300"/>
|
|
401
|
-
${makeRow(['View Name', 'Elements Count', 'Documentation'], true)}
|
|
402
|
-
${views.map(v => makeRow([v.name, String(v.elements.length), v.documentation])).join('')}
|
|
403
|
-
</Table>
|
|
404
|
-
</Worksheet>
|
|
405
|
-
|
|
406
|
-
<Worksheet ss:Name="Elements">
|
|
407
|
-
<Table>
|
|
408
|
-
<Column ss:Width="140"/>
|
|
409
|
-
<Column ss:Width="160"/>
|
|
410
|
-
<Column ss:Width="100"/>
|
|
411
|
-
<Column ss:Width="280"/>
|
|
412
|
-
${makeRow(['Name', 'Type', 'Layer', 'Documentation'], true)}
|
|
413
|
-
${elements.map(e => makeRow([e.name, e.type, e.layer, e.documentation])).join('')}
|
|
414
|
-
</Table>
|
|
415
|
-
</Worksheet>
|
|
416
|
-
|
|
417
|
-
<Worksheet ss:Name="Relationships">
|
|
418
|
-
<Table>
|
|
419
|
-
<Column ss:Width="140"/>
|
|
420
|
-
<Column ss:Width="160"/>
|
|
421
|
-
<Column ss:Width="160"/>
|
|
422
|
-
<Column ss:Width="100"/>
|
|
423
|
-
<Column ss:Width="200"/>
|
|
424
|
-
${makeRow(['Type', 'Source', 'Target', 'Name', 'Documentation'], true)}
|
|
425
|
-
${relationships.map(r => makeRow([r.type, r.source, r.target, r.name, r.documentation])).join('')}
|
|
426
|
-
</Table>
|
|
427
|
-
</Worksheet>
|
|
428
|
-
|
|
429
|
-
${properties.length ? `
|
|
430
|
-
<Worksheet ss:Name="Properties">
|
|
431
|
-
<Table>
|
|
432
|
-
<Column ss:Width="160"/>
|
|
433
|
-
<Column ss:Width="300"/>
|
|
434
|
-
${makeRow(['Name', 'Value'], true)}
|
|
435
|
-
${properties.map(p => makeRow([p.name, p.value])).join('')}
|
|
436
|
-
</Table>
|
|
437
|
-
</Worksheet>` : ''}
|
|
438
|
-
</Workbook>`;
|
|
178
|
+
return formatExcel(this.extractData());
|
|
439
179
|
}
|
|
440
180
|
|
|
441
181
|
/**
|
|
@@ -444,30 +184,7 @@ ${cells.map(c => ` <Cell${isHeader ? ' ss:StyleID="Header"' : ''}><Data ss:T
|
|
|
444
184
|
* @returns {string}
|
|
445
185
|
*/
|
|
446
186
|
toCsv(table = 'elements') {
|
|
447
|
-
|
|
448
|
-
switch (table) {
|
|
449
|
-
case 'views': {
|
|
450
|
-
const header = ['ID', 'Name', 'Elements', 'Documentation'];
|
|
451
|
-
const rows = data.views.map(v => [v.id, v.name, v.elements.length, v.documentation]);
|
|
452
|
-
return [header, ...rows].map(r => r.map(escapeCsv).join(',')).join('\r\n');
|
|
453
|
-
}
|
|
454
|
-
case 'relationships': {
|
|
455
|
-
const header = ['ID', 'Type', 'Source', 'Target', 'Name', 'Documentation'];
|
|
456
|
-
const rows = data.relationships.map(r => [r.id, r.type, r.source, r.target, r.name, r.documentation]);
|
|
457
|
-
return [header, ...rows].map(r => r.map(escapeCsv).join(',')).join('\r\n');
|
|
458
|
-
}
|
|
459
|
-
case 'properties': {
|
|
460
|
-
const header = ['Name', 'Value'];
|
|
461
|
-
const rows = data.properties.map(p => [p.name, p.value]);
|
|
462
|
-
return [header, ...rows].map(r => r.map(escapeCsv).join(',')).join('\r\n');
|
|
463
|
-
}
|
|
464
|
-
case 'elements':
|
|
465
|
-
default: {
|
|
466
|
-
const header = ['ID', 'Name', 'Type', 'Layer', 'Documentation'];
|
|
467
|
-
const rows = data.elements.map(e => [e.id, e.name, e.type, e.layer, e.documentation]);
|
|
468
|
-
return [header, ...rows].map(r => r.map(escapeCsv).join(',')).join('\r\n');
|
|
469
|
-
}
|
|
470
|
-
}
|
|
187
|
+
return formatCsv(this.extractData(), table);
|
|
471
188
|
}
|
|
472
189
|
|
|
473
190
|
/**
|
|
@@ -475,73 +192,6 @@ ${cells.map(c => ` <Cell${isHeader ? ' ss:StyleID="Header"' : ''}><Data ss:T
|
|
|
475
192
|
* @returns {string}
|
|
476
193
|
*/
|
|
477
194
|
toMarkdown() {
|
|
478
|
-
|
|
479
|
-
const { model, views, elements, relationships, properties, date } = data;
|
|
480
|
-
const lines = [];
|
|
481
|
-
|
|
482
|
-
lines.push(`# ${model.name}`);
|
|
483
|
-
lines.push(`*Generated: ${date} | Version: ${model.version}*`);
|
|
484
|
-
lines.push('');
|
|
485
|
-
|
|
486
|
-
if (model.documentation) {
|
|
487
|
-
lines.push(model.documentation);
|
|
488
|
-
lines.push('');
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
if (properties.length) {
|
|
492
|
-
lines.push('## Properties');
|
|
493
|
-
lines.push('');
|
|
494
|
-
lines.push('| Property | Value |');
|
|
495
|
-
lines.push('| --- | --- |');
|
|
496
|
-
for (const p of properties) {
|
|
497
|
-
lines.push(`| ${p.name} | ${p.value} |`);
|
|
498
|
-
}
|
|
499
|
-
lines.push('');
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
if (views.length) {
|
|
503
|
-
lines.push(`## Views (${views.length})`);
|
|
504
|
-
lines.push('');
|
|
505
|
-
for (const v of views) {
|
|
506
|
-
lines.push(`### ${v.name}`);
|
|
507
|
-
if (v.documentation) {
|
|
508
|
-
lines.push('');
|
|
509
|
-
lines.push(v.documentation);
|
|
510
|
-
}
|
|
511
|
-
if (v.elements.length) {
|
|
512
|
-
lines.push('');
|
|
513
|
-
lines.push('| Element | Type |');
|
|
514
|
-
lines.push('| --- | --- |');
|
|
515
|
-
for (const e of v.elements) {
|
|
516
|
-
lines.push(`| ${e.name} | ${e.type} |`);
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
lines.push('');
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
if (elements.length) {
|
|
524
|
-
lines.push(`## Elements (${elements.length})`);
|
|
525
|
-
lines.push('');
|
|
526
|
-
lines.push('| Name | Type | Layer | Documentation |');
|
|
527
|
-
lines.push('| --- | --- | --- | --- |');
|
|
528
|
-
for (const e of elements) {
|
|
529
|
-
lines.push(`| **${e.name}** | ${e.type} | ${e.layer} | ${e.documentation.replace(/\n/g, ' ')} |`);
|
|
530
|
-
}
|
|
531
|
-
lines.push('');
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
if (relationships.length) {
|
|
535
|
-
lines.push(`## Relationships (${relationships.length})`);
|
|
536
|
-
lines.push('');
|
|
537
|
-
lines.push('| Type | Source | Target | Name |');
|
|
538
|
-
lines.push('| --- | --- | --- | --- |');
|
|
539
|
-
for (const r of relationships) {
|
|
540
|
-
lines.push(`| ${r.type} | ${r.source} | ${r.target} | ${r.name} |`);
|
|
541
|
-
}
|
|
542
|
-
lines.push('');
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
return lines.join('\n');
|
|
195
|
+
return formatMarkdown(this.extractData());
|
|
546
196
|
}
|
|
547
197
|
}
|