ts-visio 1.0.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/LICENSE +21 -0
- package/README.md +343 -0
- package/dist/Layer.d.ts +12 -0
- package/dist/Layer.js +35 -0
- package/dist/Page.d.ts +30 -0
- package/dist/Page.js +169 -0
- package/dist/PageManager.d.ts +8 -0
- package/dist/PageManager.js +35 -0
- package/dist/SchemaDiagram.d.ts +22 -0
- package/dist/SchemaDiagram.js +36 -0
- package/dist/Shape.d.ts +68 -0
- package/dist/Shape.js +203 -0
- package/dist/ShapeModifier.d.ts +66 -0
- package/dist/ShapeModifier.js +889 -0
- package/dist/ShapeReader.d.ts +9 -0
- package/dist/ShapeReader.js +51 -0
- package/dist/VisioDocument.d.ts +21 -0
- package/dist/VisioDocument.js +119 -0
- package/dist/VisioPackage.d.ts +10 -0
- package/dist/VisioPackage.js +112 -0
- package/dist/core/MasterManager.d.ts +15 -0
- package/dist/core/MasterManager.js +43 -0
- package/dist/core/MediaConstants.d.ts +5 -0
- package/dist/core/MediaConstants.js +16 -0
- package/dist/core/MediaManager.d.ts +13 -0
- package/dist/core/MediaManager.js +88 -0
- package/dist/core/PageManager.d.ts +28 -0
- package/dist/core/PageManager.js +244 -0
- package/dist/core/RelsManager.d.ts +11 -0
- package/dist/core/RelsManager.js +81 -0
- package/dist/core/VisioConstants.d.ts +38 -0
- package/dist/core/VisioConstants.js +41 -0
- package/dist/core/VisioValidator.d.ts +27 -0
- package/dist/core/VisioValidator.js +362 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +32 -0
- package/dist/shapes/ConnectorBuilder.d.ts +37 -0
- package/dist/shapes/ConnectorBuilder.js +173 -0
- package/dist/shapes/ContainerBuilder.d.ts +6 -0
- package/dist/shapes/ContainerBuilder.js +103 -0
- package/dist/shapes/ForeignShapeBuilder.d.ts +4 -0
- package/dist/shapes/ForeignShapeBuilder.js +47 -0
- package/dist/shapes/ShapeBuilder.d.ts +4 -0
- package/dist/shapes/ShapeBuilder.js +68 -0
- package/dist/templates/MinimalVsdx.d.ts +10 -0
- package/dist/templates/MinimalVsdx.js +66 -0
- package/dist/types/VisioTypes.d.ts +85 -0
- package/dist/types/VisioTypes.js +14 -0
- package/dist/utils/StubHelpers.d.ts +7 -0
- package/dist/utils/StubHelpers.js +16 -0
- package/dist/utils/StyleHelpers.d.ts +30 -0
- package/dist/utils/StyleHelpers.js +95 -0
- package/dist/utils/VisioParsers.d.ts +6 -0
- package/dist/utils/VisioParsers.js +45 -0
- package/package.json +27 -0
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.VisioValidator = void 0;
|
|
4
|
+
const fast_xml_parser_1 = require("fast-xml-parser");
|
|
5
|
+
// Known Visio section names per Microsoft schema
|
|
6
|
+
const VALID_SECTION_NAMES = new Set([
|
|
7
|
+
'Geometry', 'Character', 'Paragraph', 'Tabs', 'Scratch', 'Connection',
|
|
8
|
+
'Field', 'Control', 'Action', 'Layer', 'Property', 'User', 'Hyperlink',
|
|
9
|
+
'Reviewer', 'Annotation', 'ActionTag', 'Line', 'Fill', 'FillGradient',
|
|
10
|
+
'LineGradient', 'TextXForm', 'RelQuadBezTo', 'RelCubBezTo', 'RelMoveTo',
|
|
11
|
+
'RelLineTo', 'RelEllipticalArcTo', 'InfiniteLine', 'Ellipse', 'SplineStart',
|
|
12
|
+
'SplineKnot', 'PolylineTo', 'NURBSTo'
|
|
13
|
+
]);
|
|
14
|
+
class VisioValidator {
|
|
15
|
+
constructor() {
|
|
16
|
+
this.parser = new fast_xml_parser_1.XMLParser({
|
|
17
|
+
ignoreAttributes: false,
|
|
18
|
+
attributeNamePrefix: '@_'
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Validate a VisioPackage for structural correctness
|
|
23
|
+
*/
|
|
24
|
+
async validate(pkg) {
|
|
25
|
+
const errors = [];
|
|
26
|
+
const warnings = [];
|
|
27
|
+
// 1. Check required files exist
|
|
28
|
+
this.checkRequiredFiles(pkg, errors);
|
|
29
|
+
// 2. Validate Content Types
|
|
30
|
+
this.validateContentTypes(pkg, errors, warnings);
|
|
31
|
+
// 3. Validate document relationships
|
|
32
|
+
this.validateDocumentRels(pkg, errors);
|
|
33
|
+
// 4. Validate pages
|
|
34
|
+
this.validatePages(pkg, errors, warnings);
|
|
35
|
+
// 5. Validate master references
|
|
36
|
+
this.validateMasterReferences(pkg, errors, warnings);
|
|
37
|
+
// 6. Validate relationship file integrity
|
|
38
|
+
this.validateRelationshipIntegrity(pkg, errors, warnings);
|
|
39
|
+
return {
|
|
40
|
+
valid: errors.length === 0,
|
|
41
|
+
errors,
|
|
42
|
+
warnings
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
checkRequiredFiles(pkg, errors) {
|
|
46
|
+
const requiredFiles = [
|
|
47
|
+
'[Content_Types].xml',
|
|
48
|
+
'_rels/.rels',
|
|
49
|
+
'visio/document.xml',
|
|
50
|
+
'visio/_rels/document.xml.rels',
|
|
51
|
+
'visio/pages/pages.xml',
|
|
52
|
+
'visio/pages/_rels/pages.xml.rels'
|
|
53
|
+
];
|
|
54
|
+
for (const file of requiredFiles) {
|
|
55
|
+
try {
|
|
56
|
+
pkg.getFileText(file);
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
errors.push(`Missing required file: ${file}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
validateContentTypes(pkg, errors, warnings) {
|
|
64
|
+
try {
|
|
65
|
+
const content = pkg.getFileText('[Content_Types].xml');
|
|
66
|
+
const parsed = this.parser.parse(content);
|
|
67
|
+
if (!parsed.Types) {
|
|
68
|
+
errors.push('[Content_Types].xml: Missing root <Types> element');
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
const overrides = parsed.Types.Override ?
|
|
72
|
+
(Array.isArray(parsed.Types.Override) ? parsed.Types.Override : [parsed.Types.Override])
|
|
73
|
+
: [];
|
|
74
|
+
const partNames = overrides.map((o) => o['@_PartName']);
|
|
75
|
+
if (!partNames.some((p) => p?.includes('document.xml'))) {
|
|
76
|
+
warnings.push('[Content_Types].xml: Missing content type for document.xml');
|
|
77
|
+
}
|
|
78
|
+
if (!partNames.some((p) => p?.includes('pages.xml'))) {
|
|
79
|
+
warnings.push('[Content_Types].xml: Missing content type for pages.xml');
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
catch (e) {
|
|
83
|
+
errors.push(`[Content_Types].xml: ${e.message}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
validateDocumentRels(pkg, errors) {
|
|
87
|
+
try {
|
|
88
|
+
const content = pkg.getFileText('visio/_rels/document.xml.rels');
|
|
89
|
+
const parsed = this.parser.parse(content);
|
|
90
|
+
if (!parsed.Relationships) {
|
|
91
|
+
errors.push('document.xml.rels: Missing <Relationships> element');
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const rels = parsed.Relationships.Relationship ?
|
|
95
|
+
(Array.isArray(parsed.Relationships.Relationship) ? parsed.Relationships.Relationship : [parsed.Relationships.Relationship])
|
|
96
|
+
: [];
|
|
97
|
+
const pagesRel = rels.find((r) => r['@_Target']?.includes('pages'));
|
|
98
|
+
if (!pagesRel) {
|
|
99
|
+
errors.push('document.xml.rels: Missing relationship to pages.xml');
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
catch (e) {
|
|
103
|
+
errors.push(`document.xml.rels: ${e.message}`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
validatePages(pkg, errors, warnings) {
|
|
107
|
+
try {
|
|
108
|
+
const pagesContent = pkg.getFileText('visio/pages/pages.xml');
|
|
109
|
+
const parsedPages = this.parser.parse(pagesContent);
|
|
110
|
+
let pageNodes = parsedPages.Pages?.Page;
|
|
111
|
+
if (!pageNodes) {
|
|
112
|
+
warnings.push('pages.xml: No pages defined');
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
pageNodes = Array.isArray(pageNodes) ? pageNodes : [pageNodes];
|
|
116
|
+
for (const pageNode of pageNodes) {
|
|
117
|
+
const pageId = pageNode['@_ID'];
|
|
118
|
+
const pageName = pageNode['@_Name'] || `Page-${pageId}`;
|
|
119
|
+
if (!pageId) {
|
|
120
|
+
errors.push(`Page "${pageName}": Missing ID attribute`);
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
const relId = pageNode.Rel?.['@_r:id'] || pageNode['@_r:id'];
|
|
124
|
+
if (!relId) {
|
|
125
|
+
errors.push(`Page "${pageName}" (ID=${pageId}): Missing Rel element with r:id`);
|
|
126
|
+
}
|
|
127
|
+
this.validatePageContent(pkg, pageId, pageName, errors, warnings);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
catch (e) {
|
|
131
|
+
errors.push(`pages.xml: ${e.message}`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
validatePageContent(pkg, pageId, pageName, errors, warnings) {
|
|
135
|
+
const pagePath = `visio/pages/page${pageId}.xml`;
|
|
136
|
+
try {
|
|
137
|
+
const content = pkg.getFileText(pagePath);
|
|
138
|
+
const parsed = this.parser.parse(content);
|
|
139
|
+
if (!parsed.PageContents) {
|
|
140
|
+
errors.push(`${pagePath}: Missing <PageContents> element`);
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
this.validateShapeIds(parsed, pagePath, errors);
|
|
144
|
+
this.validateConnects(parsed, pagePath, errors);
|
|
145
|
+
this.validateSectionNames(parsed, pagePath, warnings);
|
|
146
|
+
this.validateCellNames(parsed, pagePath, warnings);
|
|
147
|
+
this.validateImageShapes(pkg, pageId, parsed, pagePath, errors, warnings);
|
|
148
|
+
}
|
|
149
|
+
catch (e) {
|
|
150
|
+
errors.push(`${pagePath}: ${e.message}`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
validateShapeIds(parsed, pagePath, errors) {
|
|
154
|
+
const shapes = this.getAllShapes(parsed);
|
|
155
|
+
const ids = new Set();
|
|
156
|
+
for (const shape of shapes) {
|
|
157
|
+
const id = shape['@_ID'];
|
|
158
|
+
if (!id) {
|
|
159
|
+
errors.push(`${pagePath}: Shape missing ID attribute`);
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
if (ids.has(id)) {
|
|
163
|
+
errors.push(`${pagePath}: Duplicate shape ID: ${id}`);
|
|
164
|
+
}
|
|
165
|
+
ids.add(id);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
validateConnects(parsed, pagePath, errors) {
|
|
169
|
+
if (!parsed.PageContents.Connects?.Connect)
|
|
170
|
+
return;
|
|
171
|
+
const shapes = this.getAllShapes(parsed);
|
|
172
|
+
const shapeIds = new Set(shapes.map((s) => s['@_ID']));
|
|
173
|
+
let connects = parsed.PageContents.Connects.Connect;
|
|
174
|
+
connects = Array.isArray(connects) ? connects : [connects];
|
|
175
|
+
for (const connect of connects) {
|
|
176
|
+
const fromSheet = connect['@_FromSheet'];
|
|
177
|
+
const toSheet = connect['@_ToSheet'];
|
|
178
|
+
if (fromSheet && !shapeIds.has(fromSheet)) {
|
|
179
|
+
errors.push(`${pagePath}: Connect references non-existent FromSheet: ${fromSheet}`);
|
|
180
|
+
}
|
|
181
|
+
if (toSheet && !shapeIds.has(toSheet)) {
|
|
182
|
+
errors.push(`${pagePath}: Connect references non-existent ToSheet: ${toSheet}`);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
validateSectionNames(parsed, pagePath, warnings) {
|
|
187
|
+
const shapes = this.getAllShapes(parsed);
|
|
188
|
+
for (const shape of shapes) {
|
|
189
|
+
if (!shape.Section)
|
|
190
|
+
continue;
|
|
191
|
+
const sections = Array.isArray(shape.Section) ? shape.Section : [shape.Section];
|
|
192
|
+
for (const section of sections) {
|
|
193
|
+
const name = section['@_N'];
|
|
194
|
+
if (!name) {
|
|
195
|
+
warnings.push(`${pagePath}: Shape ${shape['@_ID']} has Section without N attribute`);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
validateCellNames(parsed, pagePath, warnings) {
|
|
201
|
+
const shapes = this.getAllShapes(parsed);
|
|
202
|
+
for (const shape of shapes) {
|
|
203
|
+
// Check top-level cells
|
|
204
|
+
if (shape.Cell) {
|
|
205
|
+
const cells = Array.isArray(shape.Cell) ? shape.Cell : [shape.Cell];
|
|
206
|
+
for (const cell of cells) {
|
|
207
|
+
if (!cell['@_N']) {
|
|
208
|
+
warnings.push(`${pagePath}: Shape ${shape['@_ID']} has Cell without N attribute`);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
// Check cells in sections
|
|
213
|
+
if (shape.Section) {
|
|
214
|
+
const sections = Array.isArray(shape.Section) ? shape.Section : [shape.Section];
|
|
215
|
+
for (const section of sections) {
|
|
216
|
+
if (section.Cell) {
|
|
217
|
+
const cells = Array.isArray(section.Cell) ? section.Cell : [section.Cell];
|
|
218
|
+
for (const cell of cells) {
|
|
219
|
+
if (!cell['@_N']) {
|
|
220
|
+
warnings.push(`${pagePath}: Shape ${shape['@_ID']} Section ${section['@_N']} has Cell without N attribute`);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
if (section.Row) {
|
|
225
|
+
const rows = Array.isArray(section.Row) ? section.Row : [section.Row];
|
|
226
|
+
for (const row of rows) {
|
|
227
|
+
if (row.Cell) {
|
|
228
|
+
const cells = Array.isArray(row.Cell) ? row.Cell : [row.Cell];
|
|
229
|
+
for (const cell of cells) {
|
|
230
|
+
if (!cell['@_N']) {
|
|
231
|
+
warnings.push(`${pagePath}: Shape ${shape['@_ID']} Row ${row['@_N'] || row['@_IX']} has Cell without N attribute`);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
validateImageShapes(pkg, pageId, parsed, pagePath, errors, warnings) {
|
|
242
|
+
const shapes = this.getAllShapes(parsed);
|
|
243
|
+
for (const shape of shapes) {
|
|
244
|
+
if (shape['@_Type'] !== 'Foreign')
|
|
245
|
+
continue;
|
|
246
|
+
if (!shape.ForeignData) {
|
|
247
|
+
warnings.push(`${pagePath}: Foreign shape ${shape['@_ID']} missing ForeignData`);
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
const relId = shape.ForeignData.Rel?.['@_r:id'];
|
|
251
|
+
if (relId) {
|
|
252
|
+
try {
|
|
253
|
+
const relsPath = `visio/pages/_rels/page${pageId}.xml.rels`;
|
|
254
|
+
const relsContent = pkg.getFileText(relsPath);
|
|
255
|
+
const parsedRels = this.parser.parse(relsContent);
|
|
256
|
+
let rels = parsedRels.Relationships?.Relationship || [];
|
|
257
|
+
rels = Array.isArray(rels) ? rels : [rels];
|
|
258
|
+
const found = rels.find((r) => r['@_Id'] === relId);
|
|
259
|
+
if (!found) {
|
|
260
|
+
errors.push(`${pagePath}: Image shape ${shape['@_ID']} references non-existent relationship: ${relId}`);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
catch {
|
|
264
|
+
// No rels file is okay if no relationships needed
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
validateMasterReferences(pkg, errors, warnings) {
|
|
270
|
+
const masterIds = new Set();
|
|
271
|
+
try {
|
|
272
|
+
const mastersContent = pkg.getFileText('visio/masters/masters.xml');
|
|
273
|
+
const parsedMasters = this.parser.parse(mastersContent);
|
|
274
|
+
let masters = parsedMasters.Masters?.Master || [];
|
|
275
|
+
masters = Array.isArray(masters) ? masters : [masters];
|
|
276
|
+
for (const master of masters) {
|
|
277
|
+
if (master['@_ID']) {
|
|
278
|
+
masterIds.add(master['@_ID']);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
catch {
|
|
283
|
+
return; // No masters.xml is okay
|
|
284
|
+
}
|
|
285
|
+
try {
|
|
286
|
+
const pagesContent = pkg.getFileText('visio/pages/pages.xml');
|
|
287
|
+
const parsedPages = this.parser.parse(pagesContent);
|
|
288
|
+
let pageNodes = parsedPages.Pages?.Page || [];
|
|
289
|
+
pageNodes = Array.isArray(pageNodes) ? pageNodes : [pageNodes];
|
|
290
|
+
for (const pageNode of pageNodes) {
|
|
291
|
+
const pageId = pageNode['@_ID'];
|
|
292
|
+
const pagePath = `visio/pages/page${pageId}.xml`;
|
|
293
|
+
try {
|
|
294
|
+
const pageContent = pkg.getFileText(pagePath);
|
|
295
|
+
const parsed = this.parser.parse(pageContent);
|
|
296
|
+
const shapes = this.getAllShapes(parsed);
|
|
297
|
+
for (const shape of shapes) {
|
|
298
|
+
const masterId = shape['@_Master'];
|
|
299
|
+
if (masterId && !masterIds.has(masterId)) {
|
|
300
|
+
errors.push(`${pagePath}: Shape ${shape['@_ID']} references non-existent Master: ${masterId}`);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
catch {
|
|
305
|
+
// Page file issues handled elsewhere
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
catch {
|
|
310
|
+
// Pages issues handled elsewhere
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
validateRelationshipIntegrity(pkg, errors, warnings) {
|
|
314
|
+
try {
|
|
315
|
+
const relsContent = pkg.getFileText('visio/pages/_rels/pages.xml.rels');
|
|
316
|
+
const parsedRels = this.parser.parse(relsContent);
|
|
317
|
+
let rels = parsedRels.Relationships?.Relationship || [];
|
|
318
|
+
rels = Array.isArray(rels) ? rels : [rels];
|
|
319
|
+
for (const rel of rels) {
|
|
320
|
+
const target = rel['@_Target'];
|
|
321
|
+
const id = rel['@_Id'];
|
|
322
|
+
if (!id) {
|
|
323
|
+
errors.push('pages.xml.rels: Relationship missing Id attribute');
|
|
324
|
+
continue;
|
|
325
|
+
}
|
|
326
|
+
if (!target) {
|
|
327
|
+
errors.push(`pages.xml.rels: Relationship ${id} missing Target attribute`);
|
|
328
|
+
continue;
|
|
329
|
+
}
|
|
330
|
+
const fullPath = `visio/pages/${target}`;
|
|
331
|
+
try {
|
|
332
|
+
pkg.getFileText(fullPath);
|
|
333
|
+
}
|
|
334
|
+
catch {
|
|
335
|
+
errors.push(`pages.xml.rels: Relationship ${id} targets non-existent file: ${target}`);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
catch {
|
|
340
|
+
// Rels file issues might be handled elsewhere
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
getAllShapes(parsed) {
|
|
344
|
+
let topLevelShapes = parsed.PageContents.Shapes?.Shape || [];
|
|
345
|
+
if (!Array.isArray(topLevelShapes)) {
|
|
346
|
+
topLevelShapes = topLevelShapes ? [topLevelShapes] : [];
|
|
347
|
+
}
|
|
348
|
+
const all = [];
|
|
349
|
+
const gather = (shapeList) => {
|
|
350
|
+
for (const s of shapeList) {
|
|
351
|
+
all.push(s);
|
|
352
|
+
if (s.Shapes?.Shape) {
|
|
353
|
+
const children = Array.isArray(s.Shapes.Shape) ? s.Shapes.Shape : [s.Shapes.Shape];
|
|
354
|
+
gather(children);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
};
|
|
358
|
+
gather(topLevelShapes);
|
|
359
|
+
return all;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
exports.VisioValidator = VisioValidator;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { VisioPackage } from './VisioPackage';
|
|
2
|
+
export { PageManager } from './PageManager';
|
|
3
|
+
export { ShapeReader } from './ShapeReader';
|
|
4
|
+
export { ShapeModifier } from './ShapeModifier';
|
|
5
|
+
export { VisioDocument } from './VisioDocument';
|
|
6
|
+
export { Page } from './Page';
|
|
7
|
+
export { Shape } from './Shape';
|
|
8
|
+
export * from './types/VisioTypes';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.Shape = exports.Page = exports.VisioDocument = exports.ShapeModifier = exports.ShapeReader = exports.PageManager = exports.VisioPackage = void 0;
|
|
18
|
+
var VisioPackage_1 = require("./VisioPackage");
|
|
19
|
+
Object.defineProperty(exports, "VisioPackage", { enumerable: true, get: function () { return VisioPackage_1.VisioPackage; } });
|
|
20
|
+
var PageManager_1 = require("./PageManager");
|
|
21
|
+
Object.defineProperty(exports, "PageManager", { enumerable: true, get: function () { return PageManager_1.PageManager; } });
|
|
22
|
+
var ShapeReader_1 = require("./ShapeReader");
|
|
23
|
+
Object.defineProperty(exports, "ShapeReader", { enumerable: true, get: function () { return ShapeReader_1.ShapeReader; } });
|
|
24
|
+
var ShapeModifier_1 = require("./ShapeModifier");
|
|
25
|
+
Object.defineProperty(exports, "ShapeModifier", { enumerable: true, get: function () { return ShapeModifier_1.ShapeModifier; } });
|
|
26
|
+
var VisioDocument_1 = require("./VisioDocument");
|
|
27
|
+
Object.defineProperty(exports, "VisioDocument", { enumerable: true, get: function () { return VisioDocument_1.VisioDocument; } });
|
|
28
|
+
var Page_1 = require("./Page");
|
|
29
|
+
Object.defineProperty(exports, "Page", { enumerable: true, get: function () { return Page_1.Page; } });
|
|
30
|
+
var Shape_1 = require("./Shape");
|
|
31
|
+
Object.defineProperty(exports, "Shape", { enumerable: true, get: function () { return Shape_1.Shape; } });
|
|
32
|
+
__exportStar(require("./types/VisioTypes"), exports);
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export declare class ConnectorBuilder {
|
|
2
|
+
private static getCellVal;
|
|
3
|
+
private static getAbsolutePos;
|
|
4
|
+
private static getEdgePoint;
|
|
5
|
+
static buildShapeHierarchy(parsed: any): Map<string, {
|
|
6
|
+
shape: any;
|
|
7
|
+
parent: any;
|
|
8
|
+
}>;
|
|
9
|
+
static calculateConnectorLayout(fromShapeId: string, toShapeId: string, shapeHierarchy: Map<string, {
|
|
10
|
+
shape: any;
|
|
11
|
+
parent: any;
|
|
12
|
+
}>): {
|
|
13
|
+
beginX: number;
|
|
14
|
+
beginY: number;
|
|
15
|
+
endX: number;
|
|
16
|
+
endY: number;
|
|
17
|
+
width: number;
|
|
18
|
+
angle: number;
|
|
19
|
+
};
|
|
20
|
+
static createConnectorShapeObject(id: string, layout: any, beginArrow?: string, endArrow?: string): {
|
|
21
|
+
'@_ID': string;
|
|
22
|
+
'@_NameU': string;
|
|
23
|
+
'@_Name': string;
|
|
24
|
+
'@_Type': string;
|
|
25
|
+
Cell: ({
|
|
26
|
+
'@_N': string;
|
|
27
|
+
'@_V': any;
|
|
28
|
+
'@_F'?: undefined;
|
|
29
|
+
} | {
|
|
30
|
+
'@_N': string;
|
|
31
|
+
'@_V': any;
|
|
32
|
+
'@_F': string;
|
|
33
|
+
})[];
|
|
34
|
+
Section: import("../utils/StyleHelpers").VisioSection[];
|
|
35
|
+
};
|
|
36
|
+
static addConnectorToConnects(parsed: any, connectorId: string, fromShapeId: string, toShapeId: string): void;
|
|
37
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ConnectorBuilder = void 0;
|
|
4
|
+
const StyleHelpers_1 = require("../utils/StyleHelpers");
|
|
5
|
+
class ConnectorBuilder {
|
|
6
|
+
static getCellVal(shape, name) {
|
|
7
|
+
if (!shape || !shape.Cell)
|
|
8
|
+
return '0';
|
|
9
|
+
const cells = Array.isArray(shape.Cell) ? shape.Cell : [shape.Cell];
|
|
10
|
+
const cell = cells.find((c) => c['@_N'] === name);
|
|
11
|
+
return cell ? cell['@_V'] : '0';
|
|
12
|
+
}
|
|
13
|
+
static getAbsolutePos(id, shapeHierarchy) {
|
|
14
|
+
const entry = shapeHierarchy.get(id);
|
|
15
|
+
if (!entry)
|
|
16
|
+
return { x: 0, y: 0 };
|
|
17
|
+
const shape = entry.shape;
|
|
18
|
+
const pinX = parseFloat(this.getCellVal(shape, 'PinX'));
|
|
19
|
+
const pinY = parseFloat(this.getCellVal(shape, 'PinY'));
|
|
20
|
+
if (!entry.parent) {
|
|
21
|
+
return { x: pinX, y: pinY };
|
|
22
|
+
}
|
|
23
|
+
const parentPos = this.getAbsolutePos(entry.parent['@_ID'], shapeHierarchy);
|
|
24
|
+
const parentLocPinX = parseFloat(this.getCellVal(entry.parent, 'LocPinX'));
|
|
25
|
+
const parentLocPinY = parseFloat(this.getCellVal(entry.parent, 'LocPinY'));
|
|
26
|
+
return {
|
|
27
|
+
x: (parentPos.x - parentLocPinX) + pinX,
|
|
28
|
+
y: (parentPos.y - parentLocPinY) + pinY
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
static getEdgePoint(cx, cy, w, h, targetX, targetY) {
|
|
32
|
+
const dx = targetX - cx;
|
|
33
|
+
const dy = targetY - cy;
|
|
34
|
+
if (dx === 0 && dy === 0)
|
|
35
|
+
return { x: cx, y: cy };
|
|
36
|
+
const rad = Math.atan2(dy, dx);
|
|
37
|
+
const rw = w / 2;
|
|
38
|
+
const rh = h / 2;
|
|
39
|
+
const tx = dx !== 0 ? (dx > 0 ? rw : -rw) / Math.cos(rad) : Infinity;
|
|
40
|
+
const ty = dy !== 0 ? (dy > 0 ? rh : -rh) / Math.sin(rad) : Infinity;
|
|
41
|
+
const t = Math.min(Math.abs(tx), Math.abs(ty));
|
|
42
|
+
return {
|
|
43
|
+
x: cx + t * Math.cos(rad),
|
|
44
|
+
y: cy + t * Math.sin(rad)
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
static buildShapeHierarchy(parsed) {
|
|
48
|
+
const shapeHierarchy = new Map();
|
|
49
|
+
const mapHierarchy = (shapes, parent) => {
|
|
50
|
+
for (const s of shapes) {
|
|
51
|
+
shapeHierarchy.set(s['@_ID'], { shape: s, parent });
|
|
52
|
+
if (s.Shapes && s.Shapes.Shape) {
|
|
53
|
+
const children = Array.isArray(s.Shapes.Shape) ? s.Shapes.Shape : [s.Shapes.Shape];
|
|
54
|
+
mapHierarchy(children, s);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
const topShapes = parsed.PageContents.Shapes ?
|
|
59
|
+
(Array.isArray(parsed.PageContents.Shapes.Shape) ? parsed.PageContents.Shapes.Shape : [parsed.PageContents.Shapes.Shape])
|
|
60
|
+
: [];
|
|
61
|
+
mapHierarchy(topShapes, null);
|
|
62
|
+
return shapeHierarchy;
|
|
63
|
+
}
|
|
64
|
+
static calculateConnectorLayout(fromShapeId, toShapeId, shapeHierarchy) {
|
|
65
|
+
let beginX = 0, beginY = 0, endX = 0, endY = 0;
|
|
66
|
+
let sourceGeom = null;
|
|
67
|
+
let targetGeom = null;
|
|
68
|
+
const sourceEntry = shapeHierarchy.get(fromShapeId);
|
|
69
|
+
const targetEntry = shapeHierarchy.get(toShapeId);
|
|
70
|
+
if (sourceEntry) {
|
|
71
|
+
const abs = this.getAbsolutePos(fromShapeId, shapeHierarchy);
|
|
72
|
+
const w = parseFloat(this.getCellVal(sourceEntry.shape, 'Width'));
|
|
73
|
+
const h = parseFloat(this.getCellVal(sourceEntry.shape, 'Height'));
|
|
74
|
+
sourceGeom = { x: abs.x, y: abs.y, w, h };
|
|
75
|
+
beginX = abs.x;
|
|
76
|
+
beginY = abs.y;
|
|
77
|
+
}
|
|
78
|
+
if (targetEntry) {
|
|
79
|
+
const abs = this.getAbsolutePos(toShapeId, shapeHierarchy);
|
|
80
|
+
const w = parseFloat(this.getCellVal(targetEntry.shape, 'Width'));
|
|
81
|
+
const h = parseFloat(this.getCellVal(targetEntry.shape, 'Height'));
|
|
82
|
+
targetGeom = { x: abs.x, y: abs.y, w, h };
|
|
83
|
+
endX = abs.x;
|
|
84
|
+
endY = abs.y;
|
|
85
|
+
}
|
|
86
|
+
if (sourceGeom && targetGeom) {
|
|
87
|
+
const startNode = this.getEdgePoint(sourceGeom.x, sourceGeom.y, sourceGeom.w, sourceGeom.h, targetGeom.x, targetGeom.y);
|
|
88
|
+
const endNode = this.getEdgePoint(targetGeom.x, targetGeom.y, targetGeom.w, targetGeom.h, sourceGeom.x, sourceGeom.y);
|
|
89
|
+
beginX = startNode.x;
|
|
90
|
+
beginY = startNode.y;
|
|
91
|
+
endX = endNode.x;
|
|
92
|
+
endY = endNode.y;
|
|
93
|
+
}
|
|
94
|
+
const dx = endX - beginX;
|
|
95
|
+
const dy = endY - beginY;
|
|
96
|
+
const width = Math.sqrt(dx * dx + dy * dy);
|
|
97
|
+
const angle = Math.atan2(dy, dx);
|
|
98
|
+
return { beginX, beginY, endX, endY, width, angle };
|
|
99
|
+
}
|
|
100
|
+
static createConnectorShapeObject(id, layout, beginArrow, endArrow) {
|
|
101
|
+
const { beginX, beginY, endX, endY, width, angle } = layout;
|
|
102
|
+
return {
|
|
103
|
+
'@_ID': id,
|
|
104
|
+
'@_NameU': 'Dynamic connector',
|
|
105
|
+
'@_Name': 'Dynamic connector',
|
|
106
|
+
'@_Type': 'Shape',
|
|
107
|
+
Cell: [
|
|
108
|
+
{ '@_N': 'BeginX', '@_V': beginX.toString() },
|
|
109
|
+
{ '@_N': 'BeginY', '@_V': beginY.toString() },
|
|
110
|
+
{ '@_N': 'EndX', '@_V': endX.toString() },
|
|
111
|
+
{ '@_N': 'EndY', '@_V': endY.toString() },
|
|
112
|
+
{ '@_N': 'PinX', '@_V': ((beginX + endX) / 2).toString(), '@_F': '(BeginX+EndX)/2' },
|
|
113
|
+
{ '@_N': 'PinY', '@_V': ((beginY + endY) / 2).toString(), '@_F': '(BeginY+EndY)/2' },
|
|
114
|
+
{ '@_N': 'Width', '@_V': width.toString(), '@_F': 'SQRT((EndX-BeginX)^2+(EndY-BeginY)^2)' },
|
|
115
|
+
{ '@_N': 'Height', '@_V': '0' },
|
|
116
|
+
{ '@_N': 'Angle', '@_V': angle.toString(), '@_F': 'ATAN2(EndY-BeginY,EndX-BeginX)' },
|
|
117
|
+
{ '@_N': 'LocPinX', '@_V': (width * 0.5).toString(), '@_F': 'Width*0.5' },
|
|
118
|
+
{ '@_N': 'LocPinY', '@_V': '0', '@_F': 'Height*0.5' },
|
|
119
|
+
{ '@_N': 'ObjType', '@_V': '2' },
|
|
120
|
+
{ '@_N': 'ShapePermeableX', '@_V': '0' },
|
|
121
|
+
{ '@_N': 'ShapePermeableY', '@_V': '0' },
|
|
122
|
+
{ '@_N': 'ShapeRouteStyle', '@_V': '1' },
|
|
123
|
+
{ '@_N': 'ConFixedCode', '@_V': '0' }
|
|
124
|
+
],
|
|
125
|
+
Section: [
|
|
126
|
+
(0, StyleHelpers_1.createLineSection)({
|
|
127
|
+
color: '#000000',
|
|
128
|
+
weight: '0.01',
|
|
129
|
+
beginArrow: beginArrow || '0',
|
|
130
|
+
beginArrowSize: '2',
|
|
131
|
+
endArrow: endArrow || '0',
|
|
132
|
+
endArrowSize: '2'
|
|
133
|
+
}),
|
|
134
|
+
{
|
|
135
|
+
'@_N': 'Geometry',
|
|
136
|
+
'@_IX': '0',
|
|
137
|
+
Row: [
|
|
138
|
+
{ '@_T': 'MoveTo', '@_IX': '1', Cell: [{ '@_N': 'X', '@_V': '0' }, { '@_N': 'Y', '@_V': '0' }] },
|
|
139
|
+
{ '@_T': 'LineTo', '@_IX': '2', Cell: [{ '@_N': 'X', '@_V': width.toString(), '@_F': 'Width' }, { '@_N': 'Y', '@_V': '0', '@_F': 'Height*0' }] }
|
|
140
|
+
]
|
|
141
|
+
}
|
|
142
|
+
]
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
static addConnectorToConnects(parsed, connectorId, fromShapeId, toShapeId) {
|
|
146
|
+
if (!parsed.PageContents.Connects) {
|
|
147
|
+
parsed.PageContents.Connects = { Connect: [] };
|
|
148
|
+
}
|
|
149
|
+
let connectCollection = parsed.PageContents.Connects.Connect;
|
|
150
|
+
// Ensure it's an array if it was a single object or undefined
|
|
151
|
+
if (!Array.isArray(connectCollection)) {
|
|
152
|
+
connectCollection = connectCollection ? [connectCollection] : [];
|
|
153
|
+
parsed.PageContents.Connects.Connect = connectCollection;
|
|
154
|
+
}
|
|
155
|
+
connectCollection.push({
|
|
156
|
+
'@_FromSheet': connectorId,
|
|
157
|
+
'@_FromCell': 'BeginX',
|
|
158
|
+
'@_FromPart': '9',
|
|
159
|
+
'@_ToSheet': fromShapeId,
|
|
160
|
+
'@_ToCell': 'PinX',
|
|
161
|
+
'@_ToPart': '3'
|
|
162
|
+
});
|
|
163
|
+
connectCollection.push({
|
|
164
|
+
'@_FromSheet': connectorId,
|
|
165
|
+
'@_FromCell': 'EndX',
|
|
166
|
+
'@_FromPart': '12',
|
|
167
|
+
'@_ToSheet': toShapeId,
|
|
168
|
+
'@_ToCell': 'PinX',
|
|
169
|
+
'@_ToPart': '3'
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
exports.ConnectorBuilder = ConnectorBuilder;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { NewShapeProps } from '../types/VisioTypes';
|
|
2
|
+
export declare class ContainerBuilder {
|
|
3
|
+
static createContainerShape(id: string, props: NewShapeProps): any;
|
|
4
|
+
static makeContainer(shape: any): void;
|
|
5
|
+
static makeList(shape: any, direction?: 'vertical' | 'horizontal'): void;
|
|
6
|
+
}
|