ts-visio 1.10.0 → 1.13.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/README.md +66 -3
- package/dist/Layer.d.ts +16 -1
- package/dist/Layer.js +34 -1
- package/dist/Page.d.ts +11 -0
- package/dist/Page.js +17 -0
- package/dist/Shape.d.ts +24 -0
- package/dist/Shape.js +33 -1
- package/dist/ShapeModifier.d.ts +19 -0
- package/dist/ShapeModifier.js +97 -24
- package/dist/ShapeReader.d.ts +6 -0
- package/dist/ShapeReader.js +29 -3
- package/dist/VisioDocument.d.ts +24 -0
- package/dist/VisioDocument.js +42 -0
- package/dist/core/PageManager.d.ts +15 -0
- package/dist/core/PageManager.js +101 -0
- package/dist/core/VisioConstants.d.ts +30 -0
- package/dist/core/VisioConstants.js +31 -1
- package/dist/core/VisioValidator.js +2 -1
- package/dist/shapes/ShapeBuilder.js +3 -2
- package/dist/utils/StubHelpers.d.ts +1 -0
- package/dist/utils/StubHelpers.js +2 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -28,6 +28,8 @@ Built using specific schema-level abstractions to handle the complex internal st
|
|
|
28
28
|
- **Named Connection Points**: Define specific ports on shapes (`Top`, `Right`, etc.) and connect to them precisely using `fromPort`/`toPort` on any connector API.
|
|
29
29
|
- **StyleSheets**: Create document-level named styles with fill, line, and text properties via `doc.createStyle()` and apply them to shapes at creation time (`styleId`) or post-creation (`shape.applyStyle()`).
|
|
30
30
|
- **Color Palette**: Register named colors in the document's color table via `doc.addColor()` and look them up by index or hex value with `doc.getColors()` / `doc.getColorIndex()`.
|
|
31
|
+
- **Read Layers Back**: Enumerate existing layers from loaded files via `page.getLayers()`; delete a layer with `layer.delete()`, rename with `layer.rename()`, and read `layer.visible` / `layer.locked` state.
|
|
32
|
+
- **Group Traversal**: Access nested child shapes via `shape.getChildren()`, check `shape.isGroup`, and read `shape.type`.
|
|
31
33
|
|
|
32
34
|
Feature gaps are being tracked in [FEATURES.md](./FEATURES.md).
|
|
33
35
|
|
|
@@ -316,12 +318,12 @@ await shape.toUrl('https://google.com')
|
|
|
316
318
|
```
|
|
317
319
|
|
|
318
320
|
#### 17. Layers
|
|
319
|
-
Organize complex diagrams with layers. Control visibility and locking programmatically.
|
|
321
|
+
Organize complex diagrams with layers. Control visibility and locking programmatically, and read layers back from loaded files.
|
|
320
322
|
|
|
321
323
|
```typescript
|
|
322
324
|
// 1. Define Layers
|
|
323
|
-
const wireframe
|
|
324
|
-
const annotations = await page.addLayer('Annotations');
|
|
325
|
+
const wireframe = await page.addLayer('Wireframe');
|
|
326
|
+
const annotations = await page.addLayer('Annotations', { visible: false });
|
|
325
327
|
|
|
326
328
|
// 2. Assign Shapes to Layers
|
|
327
329
|
await shape.addToLayer(wireframe);
|
|
@@ -333,6 +335,21 @@ await annotations.show(); // Show again
|
|
|
333
335
|
|
|
334
336
|
// 4. Lock Layer
|
|
335
337
|
await wireframe.setLocked(true);
|
|
338
|
+
|
|
339
|
+
// 5. Read all layers back (works on loaded files too)
|
|
340
|
+
const layers = page.getLayers();
|
|
341
|
+
// [ { name: 'Wireframe', index: 0, visible: true, locked: true },
|
|
342
|
+
// { name: 'Annotations', index: 1, visible: true, locked: false } ]
|
|
343
|
+
|
|
344
|
+
for (const layer of layers) {
|
|
345
|
+
console.log(layer.name, layer.visible, layer.locked);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// 6. Rename a layer
|
|
349
|
+
await wireframe.rename('Structural');
|
|
350
|
+
|
|
351
|
+
// 7. Delete a layer (cleans up shape assignments automatically)
|
|
352
|
+
await annotations.delete();
|
|
336
353
|
```
|
|
337
354
|
|
|
338
355
|
#### 18. Cross-Functional Flowcharts (Swimlanes)
|
|
@@ -728,6 +745,52 @@ Built-in colors: IX 0 = `#000000` (black), IX 1 = `#FFFFFF` (white). User colors
|
|
|
728
745
|
|
|
729
746
|
---
|
|
730
747
|
|
|
748
|
+
#### 32. Group Traversal (`shape.getChildren()`)
|
|
749
|
+
Access nested child shapes of a group without touching XML. Only **direct** children are returned — call `getChildren()` recursively to walk a deeper tree.
|
|
750
|
+
|
|
751
|
+
```typescript
|
|
752
|
+
// 1. Create a group with children
|
|
753
|
+
const group = await page.addShape({
|
|
754
|
+
text: 'Container', x: 5, y: 5, width: 6, height: 6, type: 'Group'
|
|
755
|
+
});
|
|
756
|
+
const childA = await page.addShape({ text: 'A', x: 1, y: 1, width: 2, height: 1 }, group.id);
|
|
757
|
+
const childB = await page.addShape({ text: 'B', x: 1, y: 3, width: 2, height: 1 }, group.id);
|
|
758
|
+
|
|
759
|
+
// 2. Check if a shape is a group
|
|
760
|
+
console.log(group.isGroup); // true
|
|
761
|
+
console.log(childA.isGroup); // false
|
|
762
|
+
console.log(group.type); // 'Group'
|
|
763
|
+
|
|
764
|
+
// 3. Get direct children
|
|
765
|
+
const children = group.getChildren();
|
|
766
|
+
// → [Shape('A'), Shape('B')]
|
|
767
|
+
|
|
768
|
+
for (const child of children) {
|
|
769
|
+
console.log(child.text, child.id);
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
// 4. Recursively walk a nested tree
|
|
773
|
+
function walk(shape, depth = 0) {
|
|
774
|
+
console.log(' '.repeat(depth * 2) + shape.text);
|
|
775
|
+
for (const child of shape.getChildren()) {
|
|
776
|
+
walk(child, depth + 1);
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
walk(group);
|
|
780
|
+
|
|
781
|
+
// 5. Works on shapes loaded from an existing .vsdx file
|
|
782
|
+
const doc2 = await VisioDocument.load('existing.vsdx');
|
|
783
|
+
const shapes = doc2.pages[0].getShapes();
|
|
784
|
+
const groups = shapes.filter(s => s.isGroup);
|
|
785
|
+
for (const g of groups) {
|
|
786
|
+
console.log(`Group "${g.text}" has ${g.getChildren().length} children`);
|
|
787
|
+
}
|
|
788
|
+
```
|
|
789
|
+
|
|
790
|
+
`getChildren()` returns `[]` for non-group shapes. Children are full `Shape` instances — all existing methods (`setStyle()`, `getProperties()`, `delete()`, etc.) work on them.
|
|
791
|
+
|
|
792
|
+
---
|
|
793
|
+
|
|
731
794
|
## Examples
|
|
732
795
|
|
|
733
796
|
Check out the [examples](./examples) directory for complete scripts.
|
package/dist/Layer.d.ts
CHANGED
|
@@ -5,10 +5,25 @@ export declare class Layer {
|
|
|
5
5
|
index: number;
|
|
6
6
|
private pageId?;
|
|
7
7
|
private pkg?;
|
|
8
|
+
private _visible;
|
|
9
|
+
private _locked;
|
|
8
10
|
private modifier;
|
|
9
|
-
constructor(name: string, index: number, pageId?: string | undefined, pkg?: VisioPackage | undefined, modifier?: ShapeModifier);
|
|
11
|
+
constructor(name: string, index: number, pageId?: string | undefined, pkg?: VisioPackage | undefined, modifier?: ShapeModifier, _visible?: boolean, _locked?: boolean);
|
|
12
|
+
/** Whether the layer is currently visible. */
|
|
13
|
+
get visible(): boolean;
|
|
14
|
+
/** Whether the layer is currently locked. */
|
|
15
|
+
get locked(): boolean;
|
|
10
16
|
setVisible(visible: boolean): Promise<this>;
|
|
11
17
|
setLocked(locked: boolean): Promise<this>;
|
|
12
18
|
hide(): Promise<this>;
|
|
13
19
|
show(): Promise<this>;
|
|
20
|
+
/**
|
|
21
|
+
* Rename this layer.
|
|
22
|
+
*/
|
|
23
|
+
rename(newName: string): Promise<this>;
|
|
24
|
+
/**
|
|
25
|
+
* Delete this layer from the page.
|
|
26
|
+
* Removes the layer definition and strips it from all shape LayerMember cells.
|
|
27
|
+
*/
|
|
28
|
+
delete(): Promise<void>;
|
|
14
29
|
}
|
package/dist/Layer.js
CHANGED
|
@@ -3,18 +3,29 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.Layer = void 0;
|
|
4
4
|
const ShapeModifier_1 = require("./ShapeModifier");
|
|
5
5
|
class Layer {
|
|
6
|
-
constructor(name, index, pageId, pkg, modifier) {
|
|
6
|
+
constructor(name, index, pageId, pkg, modifier, _visible = true, _locked = false) {
|
|
7
7
|
this.name = name;
|
|
8
8
|
this.index = index;
|
|
9
9
|
this.pageId = pageId;
|
|
10
10
|
this.pkg = pkg;
|
|
11
|
+
this._visible = _visible;
|
|
12
|
+
this._locked = _locked;
|
|
11
13
|
this.modifier = modifier ?? (pkg ? new ShapeModifier_1.ShapeModifier(pkg) : null);
|
|
12
14
|
}
|
|
15
|
+
/** Whether the layer is currently visible. */
|
|
16
|
+
get visible() {
|
|
17
|
+
return this._visible;
|
|
18
|
+
}
|
|
19
|
+
/** Whether the layer is currently locked. */
|
|
20
|
+
get locked() {
|
|
21
|
+
return this._locked;
|
|
22
|
+
}
|
|
13
23
|
async setVisible(visible) {
|
|
14
24
|
if (!this.pageId || !this.modifier) {
|
|
15
25
|
throw new Error('Layer was not created with page context. Cannot update properties.');
|
|
16
26
|
}
|
|
17
27
|
await this.modifier.updateLayerProperty(this.pageId, this.index, 'Visible', visible ? '1' : '0');
|
|
28
|
+
this._visible = visible;
|
|
18
29
|
return this;
|
|
19
30
|
}
|
|
20
31
|
async setLocked(locked) {
|
|
@@ -22,6 +33,7 @@ class Layer {
|
|
|
22
33
|
throw new Error('Layer was not created with page context. Cannot update properties.');
|
|
23
34
|
}
|
|
24
35
|
await this.modifier.updateLayerProperty(this.pageId, this.index, 'Lock', locked ? '1' : '0');
|
|
36
|
+
this._locked = locked;
|
|
25
37
|
return this;
|
|
26
38
|
}
|
|
27
39
|
async hide() {
|
|
@@ -30,5 +42,26 @@ class Layer {
|
|
|
30
42
|
async show() {
|
|
31
43
|
return this.setVisible(true);
|
|
32
44
|
}
|
|
45
|
+
/**
|
|
46
|
+
* Rename this layer.
|
|
47
|
+
*/
|
|
48
|
+
async rename(newName) {
|
|
49
|
+
if (!this.pageId || !this.modifier) {
|
|
50
|
+
throw new Error('Layer was not created with page context. Cannot update properties.');
|
|
51
|
+
}
|
|
52
|
+
await this.modifier.updateLayerProperty(this.pageId, this.index, 'Name', newName);
|
|
53
|
+
this.name = newName;
|
|
54
|
+
return this;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Delete this layer from the page.
|
|
58
|
+
* Removes the layer definition and strips it from all shape LayerMember cells.
|
|
59
|
+
*/
|
|
60
|
+
async delete() {
|
|
61
|
+
if (!this.pageId || !this.modifier) {
|
|
62
|
+
throw new Error('Layer was not created with page context. Cannot delete.');
|
|
63
|
+
}
|
|
64
|
+
this.modifier.deleteLayer(this.pageId, this.index);
|
|
65
|
+
}
|
|
33
66
|
}
|
|
34
67
|
exports.Layer = Layer;
|
package/dist/Page.d.ts
CHANGED
|
@@ -77,4 +77,15 @@ export declare class Page {
|
|
|
77
77
|
lock?: boolean;
|
|
78
78
|
print?: boolean;
|
|
79
79
|
}): Promise<Layer>;
|
|
80
|
+
/**
|
|
81
|
+
* Return all layers defined on this page, ordered by index.
|
|
82
|
+
* Works for both newly created documents and loaded `.vsdx` files.
|
|
83
|
+
*
|
|
84
|
+
* @example
|
|
85
|
+
* const layers = page.getLayers();
|
|
86
|
+
* // [{ name: 'Background', index: 0, visible: true, locked: false }, ...]
|
|
87
|
+
*/
|
|
88
|
+
getLayers(): Layer[];
|
|
89
|
+
/** @internal Used by VisioDocument.renamePage() to keep in-memory state in sync. */
|
|
90
|
+
_updateName(newName: string): void;
|
|
80
91
|
}
|
package/dist/Page.js
CHANGED
|
@@ -113,6 +113,7 @@ class Page {
|
|
|
113
113
|
// In a real scenario, we might want to re-read the shape from disk to get full defaults
|
|
114
114
|
const internalStub = (0, StubHelpers_1.createVisioShapeStub)({
|
|
115
115
|
ID: newId,
|
|
116
|
+
Type: props.type,
|
|
116
117
|
Text: props.text,
|
|
117
118
|
Cells: {
|
|
118
119
|
'Width': props.width,
|
|
@@ -267,5 +268,21 @@ class Page {
|
|
|
267
268
|
const info = await this.modifier.addLayer(this.id, name, options);
|
|
268
269
|
return new Layer_1.Layer(info.name, info.index, this.id, this.pkg, this.modifier);
|
|
269
270
|
}
|
|
271
|
+
/**
|
|
272
|
+
* Return all layers defined on this page, ordered by index.
|
|
273
|
+
* Works for both newly created documents and loaded `.vsdx` files.
|
|
274
|
+
*
|
|
275
|
+
* @example
|
|
276
|
+
* const layers = page.getLayers();
|
|
277
|
+
* // [{ name: 'Background', index: 0, visible: true, locked: false }, ...]
|
|
278
|
+
*/
|
|
279
|
+
getLayers() {
|
|
280
|
+
const infos = this.modifier.getPageLayers(this.id);
|
|
281
|
+
return infos.map(l => new Layer_1.Layer(l.name, l.index, this.id, this.pkg, this.modifier, l.visible, l.locked));
|
|
282
|
+
}
|
|
283
|
+
/** @internal Used by VisioDocument.renamePage() to keep in-memory state in sync. */
|
|
284
|
+
_updateName(newName) {
|
|
285
|
+
this.internalPage.Name = newName;
|
|
286
|
+
}
|
|
270
287
|
}
|
|
271
288
|
exports.Page = Page;
|
package/dist/Shape.d.ts
CHANGED
|
@@ -23,6 +23,16 @@ export declare class Shape {
|
|
|
23
23
|
constructor(internalShape: VisioShape, pageId: string, pkg: VisioPackage, modifier?: ShapeModifier);
|
|
24
24
|
get id(): string;
|
|
25
25
|
get name(): string;
|
|
26
|
+
/**
|
|
27
|
+
* The shape's Type attribute — `'Group'` for group shapes, `'Shape'` (or `undefined`
|
|
28
|
+
* normalised to `'Shape'`) for regular shapes.
|
|
29
|
+
*/
|
|
30
|
+
get type(): string;
|
|
31
|
+
/**
|
|
32
|
+
* `true` if this shape is a Group (i.e. it can contain nested child shapes).
|
|
33
|
+
* Use `shape.getChildren()` to retrieve those children.
|
|
34
|
+
*/
|
|
35
|
+
get isGroup(): boolean;
|
|
26
36
|
get text(): string;
|
|
27
37
|
setText(newText: string): Promise<void>;
|
|
28
38
|
get width(): number;
|
|
@@ -73,6 +83,20 @@ export declare class Shape {
|
|
|
73
83
|
* Returns an empty array if the shape has no layer assignment.
|
|
74
84
|
*/
|
|
75
85
|
getLayerIndices(): number[];
|
|
86
|
+
/**
|
|
87
|
+
* Return the direct child shapes of this group.
|
|
88
|
+
* Returns an empty array for non-group shapes or groups with no children.
|
|
89
|
+
*
|
|
90
|
+
* Only direct children are returned — grandchildren are accessible by calling
|
|
91
|
+
* `getChildren()` on the child shape.
|
|
92
|
+
*
|
|
93
|
+
* @example
|
|
94
|
+
* const group = await page.addShape({ text: 'G', x: 5, y: 5, width: 4, height: 4, type: 'Group' });
|
|
95
|
+
* await page.addShape({ text: 'Child A', x: 1, y: 1, width: 1, height: 1 }, group.id);
|
|
96
|
+
* await page.addShape({ text: 'Child B', x: 2, y: 1, width: 1, height: 1 }, group.id);
|
|
97
|
+
* group.getChildren(); // → [Shape('Child A'), Shape('Child B')]
|
|
98
|
+
*/
|
|
99
|
+
getChildren(): Shape[];
|
|
76
100
|
/** Current rotation angle in degrees (0 if no Angle cell is set). */
|
|
77
101
|
get angle(): number;
|
|
78
102
|
/**
|
package/dist/Shape.js
CHANGED
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.Shape = void 0;
|
|
4
4
|
const ShapeModifier_1 = require("./ShapeModifier");
|
|
5
5
|
const VisioTypes_1 = require("./types/VisioTypes");
|
|
6
|
+
const VisioConstants_1 = require("./core/VisioConstants");
|
|
6
7
|
/** Round a coordinate to 10 decimal places to prevent float-to-string-to-float precision drift. */
|
|
7
8
|
function fmtCoord(n) {
|
|
8
9
|
return parseFloat(n.toFixed(10)).toString();
|
|
@@ -20,6 +21,20 @@ class Shape {
|
|
|
20
21
|
get name() {
|
|
21
22
|
return this.internalShape.Name;
|
|
22
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* The shape's Type attribute — `'Group'` for group shapes, `'Shape'` (or `undefined`
|
|
26
|
+
* normalised to `'Shape'`) for regular shapes.
|
|
27
|
+
*/
|
|
28
|
+
get type() {
|
|
29
|
+
return this.internalShape.Type ?? 'Shape';
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* `true` if this shape is a Group (i.e. it can contain nested child shapes).
|
|
33
|
+
* Use `shape.getChildren()` to retrieve those children.
|
|
34
|
+
*/
|
|
35
|
+
get isGroup() {
|
|
36
|
+
return this.internalShape.Type === VisioConstants_1.SHAPE_TYPES.Group;
|
|
37
|
+
}
|
|
23
38
|
get text() {
|
|
24
39
|
return this.internalShape.Text || '';
|
|
25
40
|
}
|
|
@@ -139,6 +154,23 @@ class Shape {
|
|
|
139
154
|
getLayerIndices() {
|
|
140
155
|
return this.modifier.getShapeLayerIndices(this.pageId, this.id);
|
|
141
156
|
}
|
|
157
|
+
/**
|
|
158
|
+
* Return the direct child shapes of this group.
|
|
159
|
+
* Returns an empty array for non-group shapes or groups with no children.
|
|
160
|
+
*
|
|
161
|
+
* Only direct children are returned — grandchildren are accessible by calling
|
|
162
|
+
* `getChildren()` on the child shape.
|
|
163
|
+
*
|
|
164
|
+
* @example
|
|
165
|
+
* const group = await page.addShape({ text: 'G', x: 5, y: 5, width: 4, height: 4, type: 'Group' });
|
|
166
|
+
* await page.addShape({ text: 'Child A', x: 1, y: 1, width: 1, height: 1 }, group.id);
|
|
167
|
+
* await page.addShape({ text: 'Child B', x: 2, y: 1, width: 1, height: 1 }, group.id);
|
|
168
|
+
* group.getChildren(); // → [Shape('Child A'), Shape('Child B')]
|
|
169
|
+
*/
|
|
170
|
+
getChildren() {
|
|
171
|
+
const children = this.modifier.getShapeChildren(this.pageId, this.id);
|
|
172
|
+
return children.map(c => new Shape(c, this.pageId, this.pkg, this.modifier));
|
|
173
|
+
}
|
|
142
174
|
/** Current rotation angle in degrees (0 if no Angle cell is set). */
|
|
143
175
|
get angle() {
|
|
144
176
|
const cell = this.internalShape.Cells['Angle'];
|
|
@@ -185,7 +217,7 @@ class Shape {
|
|
|
185
217
|
return this;
|
|
186
218
|
}
|
|
187
219
|
async addMember(memberShape) {
|
|
188
|
-
await this.modifier.addRelationship(this.pageId, this.id, memberShape.id,
|
|
220
|
+
await this.modifier.addRelationship(this.pageId, this.id, memberShape.id, VisioConstants_1.STRUCT_RELATIONSHIP_TYPES.Container);
|
|
189
221
|
return this;
|
|
190
222
|
}
|
|
191
223
|
async addListItem(item) {
|
package/dist/ShapeModifier.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { VisioPackage } from './VisioPackage';
|
|
|
2
2
|
import { HorzAlign, VertAlign } from './utils/StyleHelpers';
|
|
3
3
|
import { NewShapeProps, ConnectorStyle, ConnectionTarget, ConnectionPointDef } from './types/VisioTypes';
|
|
4
4
|
import type { ShapeData, ShapeHyperlink } from './Shape';
|
|
5
|
+
import type { VisioShape } from './types/VisioTypes';
|
|
5
6
|
export declare class ShapeModifier {
|
|
6
7
|
private pkg;
|
|
7
8
|
addContainer(pageId: string, props: NewShapeProps): Promise<string>;
|
|
@@ -100,6 +101,19 @@ export declare class ShapeModifier {
|
|
|
100
101
|
}>;
|
|
101
102
|
assignLayer(pageId: string, shapeId: string, layerIndex: number): Promise<void>;
|
|
102
103
|
updateLayerProperty(pageId: string, layerIndex: number, propName: string, value: string): Promise<void>;
|
|
104
|
+
/**
|
|
105
|
+
* Return all layers defined in the page's PageSheet as plain objects.
|
|
106
|
+
*/
|
|
107
|
+
getPageLayers(pageId: string): Array<{
|
|
108
|
+
name: string;
|
|
109
|
+
index: number;
|
|
110
|
+
visible: boolean;
|
|
111
|
+
locked: boolean;
|
|
112
|
+
}>;
|
|
113
|
+
/**
|
|
114
|
+
* Delete a layer by index and remove it from all shape LayerMember cells.
|
|
115
|
+
*/
|
|
116
|
+
deleteLayer(pageId: string, layerIndex: number): void;
|
|
103
117
|
/**
|
|
104
118
|
* Read back all custom property (shape data) entries for a shape.
|
|
105
119
|
* Returns a map of property key → ShapeData, with values coerced to
|
|
@@ -128,6 +142,11 @@ export declare class ShapeModifier {
|
|
|
128
142
|
* Returns an empty array if the shape has no layer assignment.
|
|
129
143
|
*/
|
|
130
144
|
getShapeLayerIndices(pageId: string, shapeId: string): number[];
|
|
145
|
+
/**
|
|
146
|
+
* Return the direct child shapes of a group or container shape.
|
|
147
|
+
* Returns an empty array for non-group shapes or shapes with no children.
|
|
148
|
+
*/
|
|
149
|
+
getShapeChildren(pageId: string, shapeId: string): VisioShape[];
|
|
131
150
|
}
|
|
132
151
|
export interface ShapeStyle {
|
|
133
152
|
fillColor?: string;
|
package/dist/ShapeModifier.js
CHANGED
|
@@ -6,6 +6,7 @@ const StyleHelpers_1 = require("./utils/StyleHelpers");
|
|
|
6
6
|
const VisioConstants_1 = require("./core/VisioConstants");
|
|
7
7
|
const VisioTypes_1 = require("./types/VisioTypes");
|
|
8
8
|
const ConnectionPointBuilder_1 = require("./shapes/ConnectionPointBuilder");
|
|
9
|
+
const ShapeReader_1 = require("./ShapeReader");
|
|
9
10
|
const ForeignShapeBuilder_1 = require("./shapes/ForeignShapeBuilder");
|
|
10
11
|
const ShapeBuilder_1 = require("./shapes/ShapeBuilder");
|
|
11
12
|
const ConnectorBuilder_1 = require("./shapes/ConnectorBuilder");
|
|
@@ -230,9 +231,9 @@ class ShapeModifier {
|
|
|
230
231
|
if (!Array.isArray(shape.Section))
|
|
231
232
|
shape.Section = [shape.Section];
|
|
232
233
|
// Find or create Connection section
|
|
233
|
-
let connSection = shape.Section.find((s) => s['@_N'] ===
|
|
234
|
+
let connSection = shape.Section.find((s) => s['@_N'] === VisioConstants_1.SECTION_NAMES.Connection);
|
|
234
235
|
if (!connSection) {
|
|
235
|
-
connSection = { '@_N':
|
|
236
|
+
connSection = { '@_N': VisioConstants_1.SECTION_NAMES.Connection, Row: [] };
|
|
236
237
|
shape.Section.push(connSection);
|
|
237
238
|
}
|
|
238
239
|
if (!connSection.Row)
|
|
@@ -282,7 +283,7 @@ class ShapeModifier {
|
|
|
282
283
|
newId = this.getNextId(parsed);
|
|
283
284
|
}
|
|
284
285
|
let newShape;
|
|
285
|
-
if (props.type ===
|
|
286
|
+
if (props.type === VisioConstants_1.SHAPE_TYPES.Foreign && props.imgRelId) {
|
|
286
287
|
newShape = ForeignShapeBuilder_1.ForeignShapeBuilder.createImageShapeObject(newId, props.imgRelId, props);
|
|
287
288
|
// Text for foreign shapes? Usually none, but we can support it.
|
|
288
289
|
if (props.text !== undefined && props.text !== null) {
|
|
@@ -311,8 +312,8 @@ class ShapeModifier {
|
|
|
311
312
|
parent.Shapes.Shape = parent.Shapes.Shape ? [parent.Shapes.Shape] : [];
|
|
312
313
|
}
|
|
313
314
|
// Mark parent as Group if not already
|
|
314
|
-
if (parent['@_Type'] !==
|
|
315
|
-
parent['@_Type'] =
|
|
315
|
+
if (parent['@_Type'] !== VisioConstants_1.SHAPE_TYPES.Group) {
|
|
316
|
+
parent['@_Type'] = VisioConstants_1.SHAPE_TYPES.Group;
|
|
316
317
|
}
|
|
317
318
|
parent.Shapes.Shape.push(newShape);
|
|
318
319
|
}
|
|
@@ -397,7 +398,7 @@ class ShapeModifier {
|
|
|
397
398
|
// Update/Add Fill
|
|
398
399
|
if (style.fillColor) {
|
|
399
400
|
// Remove existing Fill section if any (simplified: assuming IX=0)
|
|
400
|
-
shape.Section = shape.Section.filter((s) => s['@_N'] !==
|
|
401
|
+
shape.Section = shape.Section.filter((s) => s['@_N'] !== VisioConstants_1.SECTION_NAMES.Fill);
|
|
401
402
|
shape.Section.push((0, StyleHelpers_1.createFillSection)(style.fillColor));
|
|
402
403
|
}
|
|
403
404
|
// Update/Add Line
|
|
@@ -405,7 +406,7 @@ class ShapeModifier {
|
|
|
405
406
|
|| style.lineWeight !== undefined
|
|
406
407
|
|| style.linePattern !== undefined;
|
|
407
408
|
if (hasLineProps) {
|
|
408
|
-
shape.Section = shape.Section.filter((s) => s['@_N'] !==
|
|
409
|
+
shape.Section = shape.Section.filter((s) => s['@_N'] !== VisioConstants_1.SECTION_NAMES.Line);
|
|
409
410
|
shape.Section.push((0, StyleHelpers_1.createLineSection)({
|
|
410
411
|
color: style.lineColor,
|
|
411
412
|
weight: style.lineWeight !== undefined ? (style.lineWeight / 72).toString() : undefined,
|
|
@@ -421,7 +422,7 @@ class ShapeModifier {
|
|
|
421
422
|
|| style.fontSize !== undefined
|
|
422
423
|
|| style.fontFamily !== undefined;
|
|
423
424
|
if (hasCharProps) {
|
|
424
|
-
shape.Section = shape.Section.filter((s) => s['@_N'] !==
|
|
425
|
+
shape.Section = shape.Section.filter((s) => s['@_N'] !== VisioConstants_1.SECTION_NAMES.Character);
|
|
425
426
|
shape.Section.push((0, StyleHelpers_1.createCharacterSection)({
|
|
426
427
|
bold: style.bold,
|
|
427
428
|
italic: style.italic,
|
|
@@ -438,7 +439,7 @@ class ShapeModifier {
|
|
|
438
439
|
|| style.spaceAfter !== undefined
|
|
439
440
|
|| style.lineSpacing !== undefined;
|
|
440
441
|
if (hasParagraphProps) {
|
|
441
|
-
shape.Section = shape.Section.filter((s) => s['@_N'] !==
|
|
442
|
+
shape.Section = shape.Section.filter((s) => s['@_N'] !== VisioConstants_1.SECTION_NAMES.Paragraph);
|
|
442
443
|
shape.Section.push((0, StyleHelpers_1.createParagraphSection)({
|
|
443
444
|
horzAlign: style.horzAlign,
|
|
444
445
|
spaceBefore: style.spaceBefore,
|
|
@@ -452,7 +453,7 @@ class ShapeModifier {
|
|
|
452
453
|
|| style.textMarginLeft !== undefined
|
|
453
454
|
|| style.textMarginRight !== undefined;
|
|
454
455
|
if (hasTextBlockProps) {
|
|
455
|
-
shape.Section = shape.Section.filter((s) => s['@_N'] !==
|
|
456
|
+
shape.Section = shape.Section.filter((s) => s['@_N'] !== VisioConstants_1.SECTION_NAMES.TextBlock);
|
|
456
457
|
shape.Section.push((0, StyleHelpers_1.createTextBlockSection)({
|
|
457
458
|
topMargin: style.textMarginTop,
|
|
458
459
|
bottomMargin: style.textMarginBottom,
|
|
@@ -570,7 +571,7 @@ class ShapeModifier {
|
|
|
570
571
|
if (shape.Section) {
|
|
571
572
|
const sections = Array.isArray(shape.Section) ? shape.Section : [shape.Section];
|
|
572
573
|
for (const section of sections) {
|
|
573
|
-
if (section['@_N'] !==
|
|
574
|
+
if (section['@_N'] !== VisioConstants_1.SECTION_NAMES.Geometry || !section.Row)
|
|
574
575
|
continue;
|
|
575
576
|
const rows = Array.isArray(section.Row) ? section.Row : [section.Row];
|
|
576
577
|
for (const row of rows) {
|
|
@@ -627,9 +628,9 @@ class ShapeModifier {
|
|
|
627
628
|
if (!Array.isArray(shape.Section))
|
|
628
629
|
shape.Section = [shape.Section];
|
|
629
630
|
// Find or Create Property Section
|
|
630
|
-
let propSection = shape.Section.find((s) => s['@_N'] ===
|
|
631
|
+
let propSection = shape.Section.find((s) => s['@_N'] === VisioConstants_1.SECTION_NAMES.Property);
|
|
631
632
|
if (!propSection) {
|
|
632
|
-
propSection = { '@_N':
|
|
633
|
+
propSection = { '@_N': VisioConstants_1.SECTION_NAMES.Property, Row: [] };
|
|
633
634
|
shape.Section.push(propSection);
|
|
634
635
|
}
|
|
635
636
|
// Ensure Row array exists
|
|
@@ -681,7 +682,7 @@ class ShapeModifier {
|
|
|
681
682
|
}
|
|
682
683
|
// Ensure Section array exists
|
|
683
684
|
const sections = shape.Section ? (Array.isArray(shape.Section) ? shape.Section : [shape.Section]) : [];
|
|
684
|
-
const propSection = sections.find((s) => s['@_N'] ===
|
|
685
|
+
const propSection = sections.find((s) => s['@_N'] === VisioConstants_1.SECTION_NAMES.Property);
|
|
685
686
|
if (!propSection) {
|
|
686
687
|
throw new Error(`Property definition 'Prop.${name}' does not exist on shape ${shapeId}. Call addPropertyDefinition first.`);
|
|
687
688
|
}
|
|
@@ -770,7 +771,7 @@ class ShapeModifier {
|
|
|
770
771
|
return [];
|
|
771
772
|
const relsArray = Array.isArray(rels) ? rels : [rels];
|
|
772
773
|
return relsArray
|
|
773
|
-
.filter((r) => r['@_Type'] ===
|
|
774
|
+
.filter((r) => r['@_Type'] === VisioConstants_1.STRUCT_RELATIONSHIP_TYPES.Container && r['@_ShapeID'] === containerId)
|
|
774
775
|
.map((r) => r['@_RelatedShapeID']);
|
|
775
776
|
}
|
|
776
777
|
async reorderShape(pageId, shapeId, position) {
|
|
@@ -805,7 +806,7 @@ class ShapeModifier {
|
|
|
805
806
|
const getUserVal = (name, def) => {
|
|
806
807
|
if (!listShape.Section)
|
|
807
808
|
return def;
|
|
808
|
-
const userSec = listShape.Section.find((s) => s['@_N'] ===
|
|
809
|
+
const userSec = listShape.Section.find((s) => s['@_N'] === VisioConstants_1.SECTION_NAMES.User);
|
|
809
810
|
if (!userSec || !userSec.Row)
|
|
810
811
|
return def;
|
|
811
812
|
const rows = Array.isArray(userSec.Row) ? userSec.Row : [userSec.Row];
|
|
@@ -850,7 +851,7 @@ class ShapeModifier {
|
|
|
850
851
|
// 3. Update Item Position
|
|
851
852
|
await this.updateShapePosition(pageId, itemId, newX, newY);
|
|
852
853
|
// 4. Add Relationship
|
|
853
|
-
await this.addRelationship(pageId, listId, itemId,
|
|
854
|
+
await this.addRelationship(pageId, listId, itemId, VisioConstants_1.STRUCT_RELATIONSHIP_TYPES.Container);
|
|
854
855
|
// 5. Resize List Container
|
|
855
856
|
await this.resizeContainerToFit(pageId, listId, 0.25);
|
|
856
857
|
}
|
|
@@ -946,9 +947,9 @@ class ShapeModifier {
|
|
|
946
947
|
if (!Array.isArray(pageSheet.Section))
|
|
947
948
|
pageSheet.Section = [pageSheet.Section];
|
|
948
949
|
// Find or Create Layer Section
|
|
949
|
-
let layerSection = pageSheet.Section.find((s) => s['@_N'] ===
|
|
950
|
+
let layerSection = pageSheet.Section.find((s) => s['@_N'] === VisioConstants_1.SECTION_NAMES.Layer);
|
|
950
951
|
if (!layerSection) {
|
|
951
|
-
layerSection = { '@_N':
|
|
952
|
+
layerSection = { '@_N': VisioConstants_1.SECTION_NAMES.Layer, Row: [] };
|
|
952
953
|
pageSheet.Section.push(layerSection);
|
|
953
954
|
}
|
|
954
955
|
// Ensure Row array
|
|
@@ -990,9 +991,9 @@ class ShapeModifier {
|
|
|
990
991
|
if (!Array.isArray(shape.Section))
|
|
991
992
|
shape.Section = [shape.Section];
|
|
992
993
|
// Find or Create LayerMem Section
|
|
993
|
-
let memSection = shape.Section.find((s) => s['@_N'] ===
|
|
994
|
+
let memSection = shape.Section.find((s) => s['@_N'] === VisioConstants_1.SECTION_NAMES.LayerMem);
|
|
994
995
|
if (!memSection) {
|
|
995
|
-
memSection = { '@_N':
|
|
996
|
+
memSection = { '@_N': VisioConstants_1.SECTION_NAMES.LayerMem, Row: [] };
|
|
996
997
|
shape.Section.push(memSection);
|
|
997
998
|
}
|
|
998
999
|
// Ensure Row array
|
|
@@ -1036,7 +1037,7 @@ class ShapeModifier {
|
|
|
1036
1037
|
if (!pageSheet.Section)
|
|
1037
1038
|
return;
|
|
1038
1039
|
const sections = Array.isArray(pageSheet.Section) ? pageSheet.Section : [pageSheet.Section];
|
|
1039
|
-
const layerSection = sections.find((s) => s['@_N'] ===
|
|
1040
|
+
const layerSection = sections.find((s) => s['@_N'] === VisioConstants_1.SECTION_NAMES.Layer);
|
|
1040
1041
|
if (!layerSection || !layerSection.Row)
|
|
1041
1042
|
return;
|
|
1042
1043
|
const rows = Array.isArray(layerSection.Row) ? layerSection.Row : [layerSection.Row];
|
|
@@ -1059,6 +1060,69 @@ class ShapeModifier {
|
|
|
1059
1060
|
}
|
|
1060
1061
|
this.saveParsed(pageId, parsed);
|
|
1061
1062
|
}
|
|
1063
|
+
/**
|
|
1064
|
+
* Return all layers defined in the page's PageSheet as plain objects.
|
|
1065
|
+
*/
|
|
1066
|
+
getPageLayers(pageId) {
|
|
1067
|
+
const parsed = this.getParsed(pageId);
|
|
1068
|
+
const pageSheet = parsed.PageContents?.PageSheet;
|
|
1069
|
+
if (!pageSheet?.Section)
|
|
1070
|
+
return [];
|
|
1071
|
+
const sections = Array.isArray(pageSheet.Section) ? pageSheet.Section : [pageSheet.Section];
|
|
1072
|
+
const layerSection = sections.find((s) => s['@_N'] === VisioConstants_1.SECTION_NAMES.Layer);
|
|
1073
|
+
if (!layerSection?.Row)
|
|
1074
|
+
return [];
|
|
1075
|
+
const rows = Array.isArray(layerSection.Row) ? layerSection.Row : [layerSection.Row];
|
|
1076
|
+
return rows.map((row) => {
|
|
1077
|
+
const cells = Array.isArray(row.Cell) ? row.Cell : (row.Cell ? [row.Cell] : []);
|
|
1078
|
+
const getVal = (name) => cells.find((c) => c['@_N'] === name)?.['@_V'];
|
|
1079
|
+
return {
|
|
1080
|
+
name: getVal('Name') ?? '',
|
|
1081
|
+
index: parseInt(row['@_IX'], 10),
|
|
1082
|
+
visible: getVal('Visible') !== '0',
|
|
1083
|
+
locked: getVal('Lock') === '1',
|
|
1084
|
+
};
|
|
1085
|
+
});
|
|
1086
|
+
}
|
|
1087
|
+
/**
|
|
1088
|
+
* Delete a layer by index and remove it from all shape LayerMember cells.
|
|
1089
|
+
*/
|
|
1090
|
+
deleteLayer(pageId, layerIndex) {
|
|
1091
|
+
const parsed = this.getParsed(pageId);
|
|
1092
|
+
// Remove the row from the Layer section in PageSheet
|
|
1093
|
+
const pageSheet = parsed.PageContents?.PageSheet;
|
|
1094
|
+
if (pageSheet?.Section) {
|
|
1095
|
+
const sections = Array.isArray(pageSheet.Section) ? pageSheet.Section : [pageSheet.Section];
|
|
1096
|
+
const layerSection = sections.find((s) => s['@_N'] === VisioConstants_1.SECTION_NAMES.Layer);
|
|
1097
|
+
if (layerSection?.Row) {
|
|
1098
|
+
const rows = Array.isArray(layerSection.Row) ? layerSection.Row : [layerSection.Row];
|
|
1099
|
+
layerSection.Row = rows.filter((r) => r['@_IX'] !== layerIndex.toString());
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
// Remove this layer index from every shape's LayerMember cell
|
|
1103
|
+
const idxStr = layerIndex.toString();
|
|
1104
|
+
for (const [, shape] of this.getShapeMap(parsed)) {
|
|
1105
|
+
if (!shape.Section)
|
|
1106
|
+
continue;
|
|
1107
|
+
const sections = Array.isArray(shape.Section) ? shape.Section : [shape.Section];
|
|
1108
|
+
const memSec = sections.find((s) => s['@_N'] === VisioConstants_1.SECTION_NAMES.LayerMem);
|
|
1109
|
+
if (!memSec?.Row)
|
|
1110
|
+
continue;
|
|
1111
|
+
const rows = Array.isArray(memSec.Row) ? memSec.Row : [memSec.Row];
|
|
1112
|
+
const row = rows[0];
|
|
1113
|
+
if (!row?.Cell)
|
|
1114
|
+
continue;
|
|
1115
|
+
const cells = Array.isArray(row.Cell) ? row.Cell : [row.Cell];
|
|
1116
|
+
const memberCell = cells.find((c) => c['@_N'] === 'LayerMember');
|
|
1117
|
+
if (!memberCell?.['@_V'])
|
|
1118
|
+
continue;
|
|
1119
|
+
const remaining = memberCell['@_V']
|
|
1120
|
+
.split(';')
|
|
1121
|
+
.filter((s) => s.length > 0 && s !== idxStr);
|
|
1122
|
+
memberCell['@_V'] = remaining.join(';');
|
|
1123
|
+
}
|
|
1124
|
+
this.saveParsed(pageId, parsed);
|
|
1125
|
+
}
|
|
1062
1126
|
/**
|
|
1063
1127
|
* Read back all custom property (shape data) entries for a shape.
|
|
1064
1128
|
* Returns a map of property key → ShapeData, with values coerced to
|
|
@@ -1073,7 +1137,7 @@ class ShapeModifier {
|
|
|
1073
1137
|
if (!shape.Section)
|
|
1074
1138
|
return result;
|
|
1075
1139
|
const sections = Array.isArray(shape.Section) ? shape.Section : [shape.Section];
|
|
1076
|
-
const propSection = sections.find((s) => s['@_N'] ===
|
|
1140
|
+
const propSection = sections.find((s) => s['@_N'] === VisioConstants_1.SECTION_NAMES.Property);
|
|
1077
1141
|
if (!propSection?.Row)
|
|
1078
1142
|
return result;
|
|
1079
1143
|
const rows = Array.isArray(propSection.Row) ? propSection.Row : [propSection.Row];
|
|
@@ -1188,7 +1252,7 @@ class ShapeModifier {
|
|
|
1188
1252
|
if (!shape.Section)
|
|
1189
1253
|
return [];
|
|
1190
1254
|
const sections = Array.isArray(shape.Section) ? shape.Section : [shape.Section];
|
|
1191
|
-
const memSection = sections.find((s) => s['@_N'] ===
|
|
1255
|
+
const memSection = sections.find((s) => s['@_N'] === VisioConstants_1.SECTION_NAMES.LayerMem);
|
|
1192
1256
|
if (!memSection?.Row)
|
|
1193
1257
|
return [];
|
|
1194
1258
|
const rows = Array.isArray(memSection.Row) ? memSection.Row : [memSection.Row];
|
|
@@ -1205,5 +1269,14 @@ class ShapeModifier {
|
|
|
1205
1269
|
.map((s) => parseInt(s))
|
|
1206
1270
|
.filter((n) => !isNaN(n));
|
|
1207
1271
|
}
|
|
1272
|
+
/**
|
|
1273
|
+
* Return the direct child shapes of a group or container shape.
|
|
1274
|
+
* Returns an empty array for non-group shapes or shapes with no children.
|
|
1275
|
+
*/
|
|
1276
|
+
getShapeChildren(pageId, shapeId) {
|
|
1277
|
+
const pagePath = this.getPagePath(pageId);
|
|
1278
|
+
const reader = new ShapeReader_1.ShapeReader(this.pkg);
|
|
1279
|
+
return reader.readChildShapes(pagePath, shapeId);
|
|
1280
|
+
}
|
|
1208
1281
|
}
|
|
1209
1282
|
exports.ShapeModifier = ShapeModifier;
|
package/dist/ShapeReader.d.ts
CHANGED
|
@@ -25,6 +25,12 @@ export declare class ShapeReader {
|
|
|
25
25
|
/** Decode a Visio ToPart integer string to a ConnectionTarget. */
|
|
26
26
|
private decodeToPart;
|
|
27
27
|
private gatherShapes;
|
|
28
|
+
/**
|
|
29
|
+
* Return the direct child shapes of a group or container shape.
|
|
30
|
+
* Returns an empty array if the shape has no children or does not exist.
|
|
31
|
+
*/
|
|
32
|
+
readChildShapes(path: string, parentId: string): VisioShape[];
|
|
28
33
|
private findShapeById;
|
|
34
|
+
private findRawShape;
|
|
29
35
|
private parseShape;
|
|
30
36
|
}
|
package/dist/ShapeReader.js
CHANGED
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.ShapeReader = void 0;
|
|
4
4
|
const fast_xml_parser_1 = require("fast-xml-parser");
|
|
5
5
|
const VisioParsers_1 = require("./utils/VisioParsers");
|
|
6
|
+
const VisioConstants_1 = require("./core/VisioConstants");
|
|
6
7
|
class ShapeReader {
|
|
7
8
|
constructor(pkg) {
|
|
8
9
|
this.pkg = pkg;
|
|
@@ -138,7 +139,7 @@ class ShapeReader {
|
|
|
138
139
|
let lineWeight;
|
|
139
140
|
let linePattern;
|
|
140
141
|
for (const sec of sections) {
|
|
141
|
-
if (sec['@_N'] ===
|
|
142
|
+
if (sec['@_N'] === VisioConstants_1.SECTION_NAMES.Line) {
|
|
142
143
|
const lineCells = (0, VisioParsers_1.parseCells)(sec);
|
|
143
144
|
if (lineCells['LineColor']?.V)
|
|
144
145
|
lineColor = lineCells['LineColor'].V;
|
|
@@ -190,12 +191,37 @@ class ShapeReader {
|
|
|
190
191
|
}
|
|
191
192
|
}
|
|
192
193
|
}
|
|
194
|
+
/**
|
|
195
|
+
* Return the direct child shapes of a group or container shape.
|
|
196
|
+
* Returns an empty array if the shape has no children or does not exist.
|
|
197
|
+
*/
|
|
198
|
+
readChildShapes(path, parentId) {
|
|
199
|
+
let content;
|
|
200
|
+
try {
|
|
201
|
+
content = this.pkg.getFileText(path);
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
204
|
+
return [];
|
|
205
|
+
}
|
|
206
|
+
const parsed = this.parser.parse(content);
|
|
207
|
+
const shapesData = parsed.PageContents?.Shapes?.Shape;
|
|
208
|
+
if (!shapesData)
|
|
209
|
+
return [];
|
|
210
|
+
const rawParent = this.findRawShape((0, VisioParsers_1.asArray)(shapesData), parentId);
|
|
211
|
+
if (!rawParent?.Shapes?.Shape)
|
|
212
|
+
return [];
|
|
213
|
+
return (0, VisioParsers_1.asArray)(rawParent.Shapes.Shape).map((s) => this.parseShape(s));
|
|
214
|
+
}
|
|
193
215
|
findShapeById(rawShapes, shapeId) {
|
|
216
|
+
const raw = this.findRawShape(rawShapes, shapeId);
|
|
217
|
+
return raw ? this.parseShape(raw) : undefined;
|
|
218
|
+
}
|
|
219
|
+
findRawShape(rawShapes, shapeId) {
|
|
194
220
|
for (const s of rawShapes) {
|
|
195
221
|
if (s['@_ID'] === shapeId)
|
|
196
|
-
return
|
|
222
|
+
return s;
|
|
197
223
|
if (s.Shapes?.Shape) {
|
|
198
|
-
const found = this.
|
|
224
|
+
const found = this.findRawShape((0, VisioParsers_1.asArray)(s.Shapes.Shape), shapeId);
|
|
199
225
|
if (found)
|
|
200
226
|
return found;
|
|
201
227
|
}
|
package/dist/VisioDocument.d.ts
CHANGED
|
@@ -31,6 +31,30 @@ export declare class VisioDocument {
|
|
|
31
31
|
* and any BackPage references from other pages.
|
|
32
32
|
*/
|
|
33
33
|
deletePage(page: Page): Promise<void>;
|
|
34
|
+
/**
|
|
35
|
+
* Rename a page. Updates `page.name` in-memory as well as the pages.xml record.
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* doc.renamePage(page, 'Architecture Overview');
|
|
39
|
+
*/
|
|
40
|
+
renamePage(page: Page, newName: string): void;
|
|
41
|
+
/**
|
|
42
|
+
* Move a page to a new 0-based position in the tab order.
|
|
43
|
+
* Clamps `toIndex` to the valid range automatically.
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* doc.movePage(page, 0); // move to first position
|
|
47
|
+
*/
|
|
48
|
+
movePage(page: Page, toIndex: number): void;
|
|
49
|
+
/**
|
|
50
|
+
* Duplicate a page and return the new Page object.
|
|
51
|
+
* The duplicate is inserted directly after the source page in the tab order.
|
|
52
|
+
* If `newName` is omitted, `"<original name> (Copy)"` is used.
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* const copy = await doc.duplicatePage(page, 'Page 2');
|
|
56
|
+
*/
|
|
57
|
+
duplicatePage(page: Page, newName?: string): Promise<Page>;
|
|
34
58
|
/**
|
|
35
59
|
* Read document metadata from `docProps/core.xml` and `docProps/app.xml`.
|
|
36
60
|
* Fields not present in the file are returned as `undefined`.
|
package/dist/VisioDocument.js
CHANGED
|
@@ -137,6 +137,48 @@ class VisioDocument {
|
|
|
137
137
|
await this.pageManager.deletePage(page.id);
|
|
138
138
|
this._pageCache = null;
|
|
139
139
|
}
|
|
140
|
+
/**
|
|
141
|
+
* Rename a page. Updates `page.name` in-memory as well as the pages.xml record.
|
|
142
|
+
*
|
|
143
|
+
* @example
|
|
144
|
+
* doc.renamePage(page, 'Architecture Overview');
|
|
145
|
+
*/
|
|
146
|
+
renamePage(page, newName) {
|
|
147
|
+
this.pageManager.renamePage(page.id, newName);
|
|
148
|
+
page._updateName(newName);
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Move a page to a new 0-based position in the tab order.
|
|
152
|
+
* Clamps `toIndex` to the valid range automatically.
|
|
153
|
+
*
|
|
154
|
+
* @example
|
|
155
|
+
* doc.movePage(page, 0); // move to first position
|
|
156
|
+
*/
|
|
157
|
+
movePage(page, toIndex) {
|
|
158
|
+
this.pageManager.reorderPage(page.id, toIndex);
|
|
159
|
+
this._pageCache = null;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Duplicate a page and return the new Page object.
|
|
163
|
+
* The duplicate is inserted directly after the source page in the tab order.
|
|
164
|
+
* If `newName` is omitted, `"<original name> (Copy)"` is used.
|
|
165
|
+
*
|
|
166
|
+
* @example
|
|
167
|
+
* const copy = await doc.duplicatePage(page, 'Page 2');
|
|
168
|
+
*/
|
|
169
|
+
async duplicatePage(page, newName) {
|
|
170
|
+
const resolvedName = newName ?? `${page.name} (Copy)`;
|
|
171
|
+
const newId = await this.pageManager.duplicatePage(page.id, resolvedName);
|
|
172
|
+
this._pageCache = null;
|
|
173
|
+
const pageStub = {
|
|
174
|
+
ID: newId,
|
|
175
|
+
Name: resolvedName,
|
|
176
|
+
xmlPath: `visio/pages/page${newId}.xml`,
|
|
177
|
+
Shapes: [],
|
|
178
|
+
Connects: []
|
|
179
|
+
};
|
|
180
|
+
return new Page_1.Page(pageStub, this.pkg, this.mediaManager);
|
|
181
|
+
}
|
|
140
182
|
/**
|
|
141
183
|
* Read document metadata from `docProps/core.xml` and `docProps/app.xml`.
|
|
142
184
|
* Fields not present in the file are returned as `undefined`.
|
|
@@ -28,6 +28,21 @@ export declare class PageManager {
|
|
|
28
28
|
* and any BackPage references from other pages that pointed to it.
|
|
29
29
|
*/
|
|
30
30
|
deletePage(pageId: string): Promise<void>;
|
|
31
|
+
/**
|
|
32
|
+
* Rename a page — updates Name and NameU in pages.xml.
|
|
33
|
+
*/
|
|
34
|
+
renamePage(pageId: string, newName: string): void;
|
|
35
|
+
/**
|
|
36
|
+
* Move a page to a new 0-based position in the pages.xml order (which
|
|
37
|
+
* controls the tab order in the Visio UI).
|
|
38
|
+
*/
|
|
39
|
+
reorderPage(pageId: string, toIndex: number): void;
|
|
40
|
+
/**
|
|
41
|
+
* Duplicate a page: copies the page XML (and its rels file if present),
|
|
42
|
+
* registers the new page in pages.xml and pages.xml.rels, and returns
|
|
43
|
+
* the new page ID.
|
|
44
|
+
*/
|
|
45
|
+
duplicatePage(pageId: string, newName: string): Promise<string>;
|
|
31
46
|
/**
|
|
32
47
|
* Set a background page for a foreground page
|
|
33
48
|
*/
|
package/dist/core/PageManager.js
CHANGED
|
@@ -271,6 +271,107 @@ class PageManager {
|
|
|
271
271
|
// 6. Reload the page list to reflect the deletion
|
|
272
272
|
this.load(true);
|
|
273
273
|
}
|
|
274
|
+
/**
|
|
275
|
+
* Rename a page — updates Name and NameU in pages.xml.
|
|
276
|
+
*/
|
|
277
|
+
renamePage(pageId, newName) {
|
|
278
|
+
this.load();
|
|
279
|
+
const pagesPath = 'visio/pages/pages.xml';
|
|
280
|
+
const parsed = this.parser.parse(this.pkg.getFileText(pagesPath));
|
|
281
|
+
let pageNodes = parsed.Pages.Page;
|
|
282
|
+
if (!Array.isArray(pageNodes))
|
|
283
|
+
pageNodes = pageNodes ? [pageNodes] : [];
|
|
284
|
+
const node = pageNodes.find((n) => n['@_ID'] === pageId);
|
|
285
|
+
if (!node)
|
|
286
|
+
throw new Error(`Page ${pageId} not found`);
|
|
287
|
+
node['@_Name'] = newName;
|
|
288
|
+
node['@_NameU'] = newName;
|
|
289
|
+
this.pkg.updateFile(pagesPath, (0, XmlHelper_1.buildXml)(this.builder, parsed));
|
|
290
|
+
this.load(true);
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Move a page to a new 0-based position in the pages.xml order (which
|
|
294
|
+
* controls the tab order in the Visio UI).
|
|
295
|
+
*/
|
|
296
|
+
reorderPage(pageId, toIndex) {
|
|
297
|
+
this.load();
|
|
298
|
+
const pagesPath = 'visio/pages/pages.xml';
|
|
299
|
+
const parsed = this.parser.parse(this.pkg.getFileText(pagesPath));
|
|
300
|
+
let pageNodes = parsed.Pages.Page;
|
|
301
|
+
if (!Array.isArray(pageNodes))
|
|
302
|
+
pageNodes = pageNodes ? [pageNodes] : [];
|
|
303
|
+
const fromIndex = pageNodes.findIndex((n) => n['@_ID'] === pageId);
|
|
304
|
+
if (fromIndex === -1)
|
|
305
|
+
throw new Error(`Page ${pageId} not found`);
|
|
306
|
+
const clamped = Math.max(0, Math.min(toIndex, pageNodes.length - 1));
|
|
307
|
+
const [moved] = pageNodes.splice(fromIndex, 1);
|
|
308
|
+
pageNodes.splice(clamped, 0, moved);
|
|
309
|
+
parsed.Pages.Page = pageNodes;
|
|
310
|
+
this.pkg.updateFile(pagesPath, (0, XmlHelper_1.buildXml)(this.builder, parsed));
|
|
311
|
+
this.load(true);
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Duplicate a page: copies the page XML (and its rels file if present),
|
|
315
|
+
* registers the new page in pages.xml and pages.xml.rels, and returns
|
|
316
|
+
* the new page ID.
|
|
317
|
+
*/
|
|
318
|
+
async duplicatePage(pageId, newName) {
|
|
319
|
+
this.load();
|
|
320
|
+
const source = this.pages.find(p => p.id.toString() === pageId);
|
|
321
|
+
if (!source)
|
|
322
|
+
throw new Error(`Page ${pageId} not found`);
|
|
323
|
+
// 1. Calculate new ID and paths
|
|
324
|
+
const maxId = Math.max(...this.pages.map(p => p.id));
|
|
325
|
+
const newId = maxId + 1;
|
|
326
|
+
const newFileName = `page${newId}.xml`;
|
|
327
|
+
const newPath = `visio/pages/${newFileName}`;
|
|
328
|
+
// 2. Copy page XML verbatim
|
|
329
|
+
const sourceXml = this.pkg.getFileText(source.xmlPath);
|
|
330
|
+
this.pkg.updateFile(newPath, sourceXml);
|
|
331
|
+
// 3. Copy the page's .rels file if one exists (image refs etc.)
|
|
332
|
+
const sourceFileName = source.xmlPath.split('/').pop();
|
|
333
|
+
const sourceRelsPath = `visio/pages/_rels/${sourceFileName}.rels`;
|
|
334
|
+
const newRelsPath = `visio/pages/_rels/${newFileName}.rels`;
|
|
335
|
+
try {
|
|
336
|
+
this.pkg.updateFile(newRelsPath, this.pkg.getFileText(sourceRelsPath));
|
|
337
|
+
}
|
|
338
|
+
catch { /* no rels file — fine */ }
|
|
339
|
+
// 4. Add Content Types override
|
|
340
|
+
const ctPath = '[Content_Types].xml';
|
|
341
|
+
const parsedCt = this.parser.parse(this.pkg.getFileText(ctPath));
|
|
342
|
+
if (!parsedCt.Types.Override)
|
|
343
|
+
parsedCt.Types.Override = [];
|
|
344
|
+
if (!Array.isArray(parsedCt.Types.Override))
|
|
345
|
+
parsedCt.Types.Override = [parsedCt.Types.Override];
|
|
346
|
+
parsedCt.Types.Override.push({
|
|
347
|
+
'@_PartName': `/${newPath}`,
|
|
348
|
+
'@_ContentType': VisioConstants_1.CONTENT_TYPES.VISIO_PAGE
|
|
349
|
+
});
|
|
350
|
+
this.pkg.updateFile(ctPath, (0, XmlHelper_1.buildXml)(this.builder, parsedCt));
|
|
351
|
+
// 5. Add relationship in pages.xml.rels
|
|
352
|
+
const rId = await this.relsManager.ensureRelationship('visio/pages/pages.xml', newFileName, VisioConstants_1.RELATIONSHIP_TYPES.PAGE);
|
|
353
|
+
// 6. Append entry to pages.xml (directly after the source page)
|
|
354
|
+
const pagesPath = 'visio/pages/pages.xml';
|
|
355
|
+
const parsedPages = this.parser.parse(this.pkg.getFileText(pagesPath));
|
|
356
|
+
if (!parsedPages.Pages.Page)
|
|
357
|
+
parsedPages.Pages.Page = [];
|
|
358
|
+
if (!Array.isArray(parsedPages.Pages.Page))
|
|
359
|
+
parsedPages.Pages.Page = [parsedPages.Pages.Page];
|
|
360
|
+
const newEntry = {
|
|
361
|
+
'@_ID': newId.toString(),
|
|
362
|
+
'@_Name': newName,
|
|
363
|
+
'@_NameU': newName,
|
|
364
|
+
'Rel': { '@_r:id': rId }
|
|
365
|
+
};
|
|
366
|
+
if (source.isBackground)
|
|
367
|
+
newEntry['@_Background'] = '1';
|
|
368
|
+
// Insert right after the source page so the duplicate is adjacent in the tab bar
|
|
369
|
+
const srcIdx = parsedPages.Pages.Page.findIndex((n) => n['@_ID'] === pageId);
|
|
370
|
+
parsedPages.Pages.Page.splice(srcIdx + 1, 0, newEntry);
|
|
371
|
+
this.pkg.updateFile(pagesPath, (0, XmlHelper_1.buildXml)(this.builder, parsedPages));
|
|
372
|
+
this.load(true);
|
|
373
|
+
return newId.toString();
|
|
374
|
+
}
|
|
274
375
|
/**
|
|
275
376
|
* Set a background page for a foreground page
|
|
276
377
|
*/
|
|
@@ -21,6 +21,36 @@ export declare const RELATIONSHIP_TYPES: {
|
|
|
21
21
|
readonly CORE_PROPERTIES: "http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties";
|
|
22
22
|
readonly EXTENDED_PROPERTIES: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties";
|
|
23
23
|
};
|
|
24
|
+
/** Visio shape `Type` attribute values. */
|
|
25
|
+
export declare const SHAPE_TYPES: {
|
|
26
|
+
readonly Shape: "Shape";
|
|
27
|
+
readonly Group: "Group";
|
|
28
|
+
readonly Foreign: "Foreign";
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Visio ShapeSheet section names (the `N` attribute on `<Section>` elements).
|
|
32
|
+
* Used when finding or filtering sections by name in page/master XML.
|
|
33
|
+
*/
|
|
34
|
+
export declare const SECTION_NAMES: {
|
|
35
|
+
readonly Line: "Line";
|
|
36
|
+
readonly Fill: "Fill";
|
|
37
|
+
readonly Character: "Character";
|
|
38
|
+
readonly Paragraph: "Paragraph";
|
|
39
|
+
readonly TextBlock: "TextBlock";
|
|
40
|
+
readonly Geometry: "Geometry";
|
|
41
|
+
readonly Connection: "Connection";
|
|
42
|
+
readonly Property: "Property";
|
|
43
|
+
readonly User: "User";
|
|
44
|
+
readonly LayerMem: "LayerMem";
|
|
45
|
+
readonly Layer: "Layer";
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Structural relationship types stored in `<Relationship>` elements
|
|
49
|
+
* inside Visio page XML (distinct from OPC `.rels` relationship types).
|
|
50
|
+
*/
|
|
51
|
+
export declare const STRUCT_RELATIONSHIP_TYPES: {
|
|
52
|
+
readonly Container: "Container";
|
|
53
|
+
};
|
|
24
54
|
export declare const CONTENT_TYPES: {
|
|
25
55
|
readonly PNG: "image/png";
|
|
26
56
|
readonly JPEG: "image/jpeg";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.CONTENT_TYPES = exports.RELATIONSHIP_TYPES = exports.XML_NAMESPACES = void 0;
|
|
3
|
+
exports.CONTENT_TYPES = exports.STRUCT_RELATIONSHIP_TYPES = exports.SECTION_NAMES = exports.SHAPE_TYPES = exports.RELATIONSHIP_TYPES = exports.XML_NAMESPACES = void 0;
|
|
4
4
|
exports.XML_NAMESPACES = {
|
|
5
5
|
VISIO_MAIN: 'http://schemas.microsoft.com/office/visio/2012/main',
|
|
6
6
|
RELATIONSHIPS: 'http://schemas.openxmlformats.org/package/2006/relationships',
|
|
@@ -24,6 +24,36 @@ exports.RELATIONSHIP_TYPES = {
|
|
|
24
24
|
CORE_PROPERTIES: 'http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties',
|
|
25
25
|
EXTENDED_PROPERTIES: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties'
|
|
26
26
|
};
|
|
27
|
+
/** Visio shape `Type` attribute values. */
|
|
28
|
+
exports.SHAPE_TYPES = {
|
|
29
|
+
Shape: 'Shape',
|
|
30
|
+
Group: 'Group',
|
|
31
|
+
Foreign: 'Foreign',
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Visio ShapeSheet section names (the `N` attribute on `<Section>` elements).
|
|
35
|
+
* Used when finding or filtering sections by name in page/master XML.
|
|
36
|
+
*/
|
|
37
|
+
exports.SECTION_NAMES = {
|
|
38
|
+
Line: 'Line',
|
|
39
|
+
Fill: 'Fill',
|
|
40
|
+
Character: 'Character',
|
|
41
|
+
Paragraph: 'Paragraph',
|
|
42
|
+
TextBlock: 'TextBlock',
|
|
43
|
+
Geometry: 'Geometry',
|
|
44
|
+
Connection: 'Connection',
|
|
45
|
+
Property: 'Property',
|
|
46
|
+
User: 'User',
|
|
47
|
+
LayerMem: 'LayerMem',
|
|
48
|
+
Layer: 'Layer',
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* Structural relationship types stored in `<Relationship>` elements
|
|
52
|
+
* inside Visio page XML (distinct from OPC `.rels` relationship types).
|
|
53
|
+
*/
|
|
54
|
+
exports.STRUCT_RELATIONSHIP_TYPES = {
|
|
55
|
+
Container: 'Container',
|
|
56
|
+
};
|
|
27
57
|
exports.CONTENT_TYPES = {
|
|
28
58
|
PNG: 'image/png',
|
|
29
59
|
JPEG: 'image/jpeg',
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.VisioValidator = void 0;
|
|
4
4
|
const fast_xml_parser_1 = require("fast-xml-parser");
|
|
5
|
+
const VisioConstants_1 = require("./VisioConstants");
|
|
5
6
|
// Known Visio section names per Microsoft schema
|
|
6
7
|
const VALID_SECTION_NAMES = new Set([
|
|
7
8
|
'Geometry', 'Character', 'Paragraph', 'Tabs', 'Scratch', 'Connection',
|
|
@@ -241,7 +242,7 @@ class VisioValidator {
|
|
|
241
242
|
validateImageShapes(pkg, pageId, parsed, pagePath, errors, warnings) {
|
|
242
243
|
const shapes = this.getAllShapes(parsed);
|
|
243
244
|
for (const shape of shapes) {
|
|
244
|
-
if (shape['@_Type'] !==
|
|
245
|
+
if (shape['@_Type'] !== VisioConstants_1.SHAPE_TYPES.Foreign)
|
|
245
246
|
continue;
|
|
246
247
|
if (!shape.ForeignData) {
|
|
247
248
|
warnings.push(`${pagePath}: Foreign shape ${shape['@_ID']} missing ForeignData`);
|
|
@@ -4,6 +4,7 @@ exports.ShapeBuilder = void 0;
|
|
|
4
4
|
const StyleHelpers_1 = require("../utils/StyleHelpers");
|
|
5
5
|
const GeometryBuilder_1 = require("./GeometryBuilder");
|
|
6
6
|
const ConnectionPointBuilder_1 = require("./ConnectionPointBuilder");
|
|
7
|
+
const VisioConstants_1 = require("../core/VisioConstants");
|
|
7
8
|
class ShapeBuilder {
|
|
8
9
|
static createStandardShape(id, props) {
|
|
9
10
|
// Validate dimensions
|
|
@@ -14,7 +15,7 @@ class ShapeBuilder {
|
|
|
14
15
|
'@_ID': id,
|
|
15
16
|
'@_NameU': `Sheet.${id}`,
|
|
16
17
|
'@_Name': `Sheet.${id}`,
|
|
17
|
-
'@_Type': props.type ||
|
|
18
|
+
'@_Type': props.type || VisioConstants_1.SHAPE_TYPES.Shape,
|
|
18
19
|
Cell: [
|
|
19
20
|
{ '@_N': 'PinX', '@_V': props.x.toString() },
|
|
20
21
|
{ '@_N': 'PinY', '@_V': props.y.toString() },
|
|
@@ -103,7 +104,7 @@ class ShapeBuilder {
|
|
|
103
104
|
}
|
|
104
105
|
// Add Geometry
|
|
105
106
|
// Only if NOT a Group AND NOT a Master Instance
|
|
106
|
-
if (props.type !==
|
|
107
|
+
if (props.type !== VisioConstants_1.SHAPE_TYPES.Group && !props.masterId) {
|
|
107
108
|
shape.Section.push(GeometryBuilder_1.GeometryBuilder.build(props));
|
|
108
109
|
}
|
|
109
110
|
// Handle Text if provided
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.createVisioShapeStub = createVisioShapeStub;
|
|
4
|
+
const VisioConstants_1 = require("../core/VisioConstants");
|
|
4
5
|
function createVisioShapeStub(props) {
|
|
5
6
|
return {
|
|
6
7
|
ID: props.ID,
|
|
7
8
|
Name: props.Name || `Sheet.${props.ID}`,
|
|
8
|
-
Type:
|
|
9
|
+
Type: props.Type ?? VisioConstants_1.SHAPE_TYPES.Shape,
|
|
9
10
|
Text: props.Text,
|
|
10
11
|
Cells: Object.entries(props.Cells || {}).reduce((acc, [k, v]) => {
|
|
11
12
|
acc[k] = { N: k, V: v.toString() };
|