ts-visio 1.0.2 → 1.2.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 +76 -3
- package/dist/Layer.d.ts +3 -1
- package/dist/Layer.js +6 -7
- package/dist/Page.d.ts +12 -0
- package/dist/Page.js +35 -12
- package/dist/Shape.d.ts +47 -2
- package/dist/Shape.js +119 -78
- package/dist/ShapeModifier.d.ts +53 -0
- package/dist/ShapeModifier.js +451 -267
- package/dist/ShapeReader.d.ts +12 -0
- package/dist/ShapeReader.js +59 -3
- package/dist/VisioDocument.d.ts +10 -0
- package/dist/VisioDocument.js +20 -1
- package/dist/VisioPackage.d.ts +1 -0
- package/dist/VisioPackage.js +7 -0
- package/dist/core/MasterManager.js +1 -1
- package/dist/core/MediaConstants.js +11 -2
- package/dist/core/MediaManager.js +8 -16
- package/dist/core/PageManager.d.ts +8 -1
- package/dist/core/PageManager.js +75 -16
- package/dist/core/RelsManager.js +4 -11
- package/dist/index.d.ts +3 -1
- package/dist/index.js +1 -1
- package/dist/shapes/ConnectorBuilder.js +8 -9
- package/dist/shapes/ContainerBuilder.js +8 -6
- package/dist/shapes/ForeignShapeBuilder.js +9 -6
- package/dist/shapes/ShapeBuilder.js +15 -7
- package/dist/types/VisioTypes.d.ts +12 -0
- package/dist/utils/StyleHelpers.d.ts +21 -4
- package/dist/utils/StyleHelpers.js +52 -27
- package/dist/utils/XmlHelper.d.ts +39 -0
- package/dist/utils/XmlHelper.js +56 -0
- package/package.json +2 -2
package/dist/ShapeReader.d.ts
CHANGED
|
@@ -5,5 +5,17 @@ export declare class ShapeReader {
|
|
|
5
5
|
private parser;
|
|
6
6
|
constructor(pkg: VisioPackage);
|
|
7
7
|
readShapes(path: string): VisioShape[];
|
|
8
|
+
/**
|
|
9
|
+
* Returns every shape on the page flattened into a single array,
|
|
10
|
+
* including shapes nested inside groups at any depth.
|
|
11
|
+
*/
|
|
12
|
+
readAllShapes(path: string): VisioShape[];
|
|
13
|
+
/**
|
|
14
|
+
* Find a single shape by ID anywhere in the page tree (including nested groups).
|
|
15
|
+
* Returns undefined if not found.
|
|
16
|
+
*/
|
|
17
|
+
readShapeById(path: string, shapeId: string): VisioShape | undefined;
|
|
18
|
+
private gatherShapes;
|
|
19
|
+
private findShapeById;
|
|
8
20
|
private parseShape;
|
|
9
21
|
}
|
package/dist/ShapeReader.js
CHANGED
|
@@ -20,12 +20,68 @@ class ShapeReader {
|
|
|
20
20
|
return [];
|
|
21
21
|
}
|
|
22
22
|
const parsed = this.parser.parse(content);
|
|
23
|
-
// Supports PageContents -> Shapes -> Shape or just Shapes -> Shape depending on structure
|
|
24
23
|
const shapesData = parsed.PageContents?.Shapes?.Shape;
|
|
25
24
|
if (!shapesData)
|
|
26
25
|
return [];
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
return (0, VisioParsers_1.asArray)(shapesData).map(s => this.parseShape(s));
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Returns every shape on the page flattened into a single array,
|
|
30
|
+
* including shapes nested inside groups at any depth.
|
|
31
|
+
*/
|
|
32
|
+
readAllShapes(path) {
|
|
33
|
+
let content;
|
|
34
|
+
try {
|
|
35
|
+
content = this.pkg.getFileText(path);
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return [];
|
|
39
|
+
}
|
|
40
|
+
const parsed = this.parser.parse(content);
|
|
41
|
+
const shapesData = parsed.PageContents?.Shapes?.Shape;
|
|
42
|
+
if (!shapesData)
|
|
43
|
+
return [];
|
|
44
|
+
const result = [];
|
|
45
|
+
this.gatherShapes((0, VisioParsers_1.asArray)(shapesData), result);
|
|
46
|
+
return result;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Find a single shape by ID anywhere in the page tree (including nested groups).
|
|
50
|
+
* Returns undefined if not found.
|
|
51
|
+
*/
|
|
52
|
+
readShapeById(path, shapeId) {
|
|
53
|
+
let content;
|
|
54
|
+
try {
|
|
55
|
+
content = this.pkg.getFileText(path);
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
const parsed = this.parser.parse(content);
|
|
61
|
+
const shapesData = parsed.PageContents?.Shapes?.Shape;
|
|
62
|
+
if (!shapesData)
|
|
63
|
+
return undefined;
|
|
64
|
+
return this.findShapeById((0, VisioParsers_1.asArray)(shapesData), shapeId);
|
|
65
|
+
}
|
|
66
|
+
gatherShapes(rawShapes, result) {
|
|
67
|
+
for (const s of rawShapes) {
|
|
68
|
+
result.push(this.parseShape(s));
|
|
69
|
+
if (s.Shapes?.Shape) {
|
|
70
|
+
this.gatherShapes((0, VisioParsers_1.asArray)(s.Shapes.Shape), result);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
findShapeById(rawShapes, shapeId) {
|
|
75
|
+
for (const s of rawShapes) {
|
|
76
|
+
if (s['@_ID'] === shapeId)
|
|
77
|
+
return this.parseShape(s);
|
|
78
|
+
if (s.Shapes?.Shape) {
|
|
79
|
+
const found = this.findShapeById((0, VisioParsers_1.asArray)(s.Shapes.Shape), shapeId);
|
|
80
|
+
if (found)
|
|
81
|
+
return found;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return undefined;
|
|
29
85
|
}
|
|
30
86
|
parseShape(s) {
|
|
31
87
|
const shape = {
|
package/dist/VisioDocument.d.ts
CHANGED
|
@@ -17,5 +17,15 @@ export declare class VisioDocument {
|
|
|
17
17
|
* Set a background page for a foreground page
|
|
18
18
|
*/
|
|
19
19
|
setBackgroundPage(foregroundPage: Page, backgroundPage: Page): Promise<void>;
|
|
20
|
+
/**
|
|
21
|
+
* Find a page by name. Returns undefined if no page with that name exists.
|
|
22
|
+
*/
|
|
23
|
+
getPage(name: string): Page | undefined;
|
|
24
|
+
/**
|
|
25
|
+
* Delete a page from the document.
|
|
26
|
+
* Removes the page XML, its relationships, the Content Types entry,
|
|
27
|
+
* and any BackPage references from other pages.
|
|
28
|
+
*/
|
|
29
|
+
deletePage(page: Page): Promise<void>;
|
|
20
30
|
save(filename?: string): Promise<Buffer>;
|
|
21
31
|
}
|
package/dist/VisioDocument.js
CHANGED
|
@@ -67,6 +67,7 @@ class VisioDocument {
|
|
|
67
67
|
const pageStub = {
|
|
68
68
|
ID: newId,
|
|
69
69
|
Name: name,
|
|
70
|
+
xmlPath: `visio/pages/page${newId}.xml`,
|
|
70
71
|
Shapes: [],
|
|
71
72
|
Connects: []
|
|
72
73
|
};
|
|
@@ -76,10 +77,12 @@ class VisioDocument {
|
|
|
76
77
|
if (!this._pageCache) {
|
|
77
78
|
const internalPages = this.pageManager.load();
|
|
78
79
|
this._pageCache = internalPages.map(p => {
|
|
79
|
-
// Adapter for VisioPage interface
|
|
80
80
|
const pageStub = {
|
|
81
81
|
ID: p.id.toString(),
|
|
82
82
|
Name: p.name,
|
|
83
|
+
// Thread the relationship-resolved path so that loaded files
|
|
84
|
+
// with non-sequential page filenames are handled correctly.
|
|
85
|
+
xmlPath: p.xmlPath,
|
|
83
86
|
Shapes: [],
|
|
84
87
|
Connects: [],
|
|
85
88
|
isBackground: p.isBackground,
|
|
@@ -99,6 +102,7 @@ class VisioDocument {
|
|
|
99
102
|
const pageStub = {
|
|
100
103
|
ID: newId,
|
|
101
104
|
Name: name,
|
|
105
|
+
xmlPath: `visio/pages/page${newId}.xml`,
|
|
102
106
|
Shapes: [],
|
|
103
107
|
Connects: [],
|
|
104
108
|
isBackground: true
|
|
@@ -112,6 +116,21 @@ class VisioDocument {
|
|
|
112
116
|
await this.pageManager.setBackgroundPage(foregroundPage.id, backgroundPage.id);
|
|
113
117
|
this._pageCache = null;
|
|
114
118
|
}
|
|
119
|
+
/**
|
|
120
|
+
* Find a page by name. Returns undefined if no page with that name exists.
|
|
121
|
+
*/
|
|
122
|
+
getPage(name) {
|
|
123
|
+
return this.pages.find(p => p.name === name);
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Delete a page from the document.
|
|
127
|
+
* Removes the page XML, its relationships, the Content Types entry,
|
|
128
|
+
* and any BackPage references from other pages.
|
|
129
|
+
*/
|
|
130
|
+
async deletePage(page) {
|
|
131
|
+
await this.pageManager.deletePage(page.id);
|
|
132
|
+
this._pageCache = null;
|
|
133
|
+
}
|
|
115
134
|
async save(filename) {
|
|
116
135
|
return this.pkg.save(filename);
|
|
117
136
|
}
|
package/dist/VisioPackage.d.ts
CHANGED
|
@@ -6,5 +6,6 @@ export declare class VisioPackage {
|
|
|
6
6
|
load(buffer: Buffer | ArrayBuffer | Uint8Array): Promise<void>;
|
|
7
7
|
updateFile(path: string, content: string | Buffer): void;
|
|
8
8
|
save(filename?: string): Promise<Buffer>;
|
|
9
|
+
removeFile(path: string): void;
|
|
9
10
|
getFileText(path: string): string;
|
|
10
11
|
}
|
package/dist/VisioPackage.js
CHANGED
|
@@ -98,6 +98,13 @@ class VisioPackage {
|
|
|
98
98
|
}
|
|
99
99
|
return buffer;
|
|
100
100
|
}
|
|
101
|
+
removeFile(path) {
|
|
102
|
+
if (!this.zip) {
|
|
103
|
+
throw new Error("Package not loaded");
|
|
104
|
+
}
|
|
105
|
+
this._files.delete(path);
|
|
106
|
+
this.zip.remove(path);
|
|
107
|
+
}
|
|
101
108
|
getFileText(path) {
|
|
102
109
|
const content = this._files.get(path);
|
|
103
110
|
if (content === undefined) {
|
|
@@ -24,7 +24,7 @@ class MasterManager {
|
|
|
24
24
|
const parsed = this.parser.parse(content);
|
|
25
25
|
let masterNodes = parsed.Masters ? parsed.Masters.Master : [];
|
|
26
26
|
if (!Array.isArray(masterNodes)) {
|
|
27
|
-
masterNodes = [masterNodes];
|
|
27
|
+
masterNodes = masterNodes ? [masterNodes] : [];
|
|
28
28
|
}
|
|
29
29
|
this.masters = masterNodes.map((node) => ({
|
|
30
30
|
id: node['@_ID'],
|
|
@@ -8,9 +8,18 @@ exports.MIME_TYPES = {
|
|
|
8
8
|
'jpeg': 'image/jpeg',
|
|
9
9
|
'gif': 'image/gif',
|
|
10
10
|
'bmp': 'image/bmp',
|
|
11
|
-
'tiff': 'image/tiff'
|
|
11
|
+
'tiff': 'image/tiff',
|
|
12
|
+
'emf': 'image/emf',
|
|
13
|
+
'wmf': 'image/wmf',
|
|
14
|
+
'svg': 'image/svg+xml',
|
|
15
|
+
'ico': 'image/x-icon',
|
|
16
|
+
'tif': 'image/tiff',
|
|
17
|
+
'wdp': 'image/vnd.ms-photo'
|
|
12
18
|
};
|
|
13
19
|
exports.SUPPORTED_IMAGE_EXTENSIONS = Object.keys(exports.MIME_TYPES);
|
|
20
|
+
// Extensions that are decoded as UTF-8 text; everything else is treated as binary
|
|
21
|
+
// to prevent corruption of EMF, WMF, OLE, and other binary assets in .vsdx archives.
|
|
22
|
+
const TEXT_EXTENSIONS = new Set(['xml', 'rels']);
|
|
14
23
|
function isBinaryExtension(ext) {
|
|
15
|
-
return
|
|
24
|
+
return !TEXT_EXTENSIONS.has(ext.toLowerCase());
|
|
16
25
|
}
|
|
@@ -1,30 +1,23 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.MediaManager = void 0;
|
|
4
|
-
const fast_xml_parser_1 = require("fast-xml-parser");
|
|
5
4
|
const MediaConstants_1 = require("./MediaConstants");
|
|
5
|
+
const XmlHelper_1 = require("../utils/XmlHelper");
|
|
6
|
+
const node_crypto_1 = require("node:crypto");
|
|
6
7
|
class MediaManager {
|
|
7
8
|
constructor(pkg) {
|
|
8
9
|
this.pkg = pkg;
|
|
9
10
|
this.deduplicationMap = new Map(); // hash -> paths
|
|
10
11
|
this.indexed = false;
|
|
11
|
-
this.parser =
|
|
12
|
-
|
|
13
|
-
attributeNamePrefix: "@_"
|
|
14
|
-
});
|
|
15
|
-
this.builder = new fast_xml_parser_1.XMLBuilder({
|
|
16
|
-
ignoreAttributes: false,
|
|
17
|
-
attributeNamePrefix: "@_",
|
|
18
|
-
format: true
|
|
19
|
-
});
|
|
12
|
+
this.parser = (0, XmlHelper_1.createXmlParser)();
|
|
13
|
+
this.builder = (0, XmlHelper_1.createXmlBuilder)();
|
|
20
14
|
}
|
|
21
15
|
ensureIndex() {
|
|
22
16
|
if (this.indexed)
|
|
23
17
|
return;
|
|
24
|
-
const crypto = require('crypto');
|
|
25
18
|
for (const [path, content] of this.pkg.filesMap.entries()) {
|
|
26
19
|
if (path.startsWith('visio/media/') && Buffer.isBuffer(content)) {
|
|
27
|
-
const hash =
|
|
20
|
+
const hash = (0, node_crypto_1.createHash)('sha1').update(content).digest('hex');
|
|
28
21
|
// Store relative path suitable for relationships
|
|
29
22
|
const filename = path.split('/').pop();
|
|
30
23
|
if (filename) {
|
|
@@ -39,8 +32,7 @@ class MediaManager {
|
|
|
39
32
|
}
|
|
40
33
|
addMedia(name, data) {
|
|
41
34
|
this.ensureIndex();
|
|
42
|
-
const
|
|
43
|
-
const hash = crypto.createHash('sha1').update(data).digest('hex');
|
|
35
|
+
const hash = (0, node_crypto_1.createHash)('sha1').update(data).digest('hex');
|
|
44
36
|
if (this.deduplicationMap.has(hash)) {
|
|
45
37
|
return this.deduplicationMap.get(hash);
|
|
46
38
|
}
|
|
@@ -73,14 +65,14 @@ class MediaManager {
|
|
|
73
65
|
parsed.Types.Default = [parsed.Types.Default];
|
|
74
66
|
}
|
|
75
67
|
const defaults = parsed.Types.Default;
|
|
76
|
-
const exists = defaults.some((d) => d['@_Extension']?.toLowerCase() === extension);
|
|
68
|
+
const exists = defaults.some((d) => d['@_Extension']?.toLowerCase() === extension.toLowerCase());
|
|
77
69
|
if (!exists) {
|
|
78
70
|
defaults.push({
|
|
79
71
|
'@_Extension': extension,
|
|
80
72
|
'@_ContentType': this.getContentType(extension)
|
|
81
73
|
});
|
|
82
74
|
// Array reference is already mutating object
|
|
83
|
-
const newXml = this.builder
|
|
75
|
+
const newXml = (0, XmlHelper_1.buildXml)(this.builder, parsed);
|
|
84
76
|
this.pkg.updateFile(ctPath, newXml);
|
|
85
77
|
}
|
|
86
78
|
}
|
|
@@ -5,7 +5,7 @@ export interface PageEntry {
|
|
|
5
5
|
relId: string;
|
|
6
6
|
xmlPath: string;
|
|
7
7
|
isBackground: boolean;
|
|
8
|
-
backPageId?:
|
|
8
|
+
backPageId?: string;
|
|
9
9
|
}
|
|
10
10
|
export declare class PageManager {
|
|
11
11
|
private pkg;
|
|
@@ -21,6 +21,13 @@ export declare class PageManager {
|
|
|
21
21
|
* Create a background page
|
|
22
22
|
*/
|
|
23
23
|
createBackgroundPage(name: string): Promise<string>;
|
|
24
|
+
/**
|
|
25
|
+
* Delete a page and clean up all associated XML entries.
|
|
26
|
+
* Removes the page file, its .rels file, the entry in pages.xml,
|
|
27
|
+
* the relationship in pages.xml.rels, the Content Types override,
|
|
28
|
+
* and any BackPage references from other pages that pointed to it.
|
|
29
|
+
*/
|
|
30
|
+
deletePage(pageId: string): Promise<void>;
|
|
24
31
|
/**
|
|
25
32
|
* Set a background page for a foreground page
|
|
26
33
|
*/
|
package/dist/core/PageManager.js
CHANGED
|
@@ -1,23 +1,16 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.PageManager = void 0;
|
|
4
|
-
const fast_xml_parser_1 = require("fast-xml-parser");
|
|
5
4
|
const VisioConstants_1 = require("./VisioConstants");
|
|
6
5
|
const RelsManager_1 = require("./RelsManager");
|
|
6
|
+
const XmlHelper_1 = require("../utils/XmlHelper");
|
|
7
7
|
class PageManager {
|
|
8
8
|
constructor(pkg) {
|
|
9
9
|
this.pkg = pkg;
|
|
10
10
|
this.pages = [];
|
|
11
11
|
this.loaded = false;
|
|
12
|
-
this.parser =
|
|
13
|
-
|
|
14
|
-
attributeNamePrefix: "@_"
|
|
15
|
-
});
|
|
16
|
-
this.builder = new fast_xml_parser_1.XMLBuilder({
|
|
17
|
-
ignoreAttributes: false,
|
|
18
|
-
attributeNamePrefix: "@_",
|
|
19
|
-
format: true
|
|
20
|
-
});
|
|
12
|
+
this.parser = (0, XmlHelper_1.createXmlParser)();
|
|
13
|
+
this.builder = (0, XmlHelper_1.createXmlBuilder)();
|
|
21
14
|
this.relsManager = new RelsManager_1.RelsManager(pkg);
|
|
22
15
|
}
|
|
23
16
|
load(force = false) {
|
|
@@ -72,7 +65,7 @@ class PageManager {
|
|
|
72
65
|
const bgAttr = node['@_Background'];
|
|
73
66
|
const isBackground = bgAttr === 'true' || bgAttr === '1' || bgAttr === true || bgAttr === 1;
|
|
74
67
|
const backPageAttr = node['@_BackPage'];
|
|
75
|
-
const backPageId = backPageAttr ?
|
|
68
|
+
const backPageId = backPageAttr ? backPageAttr.toString() : undefined;
|
|
76
69
|
return {
|
|
77
70
|
id: parseInt(node['@_ID']),
|
|
78
71
|
name: node['@_Name'],
|
|
@@ -127,7 +120,7 @@ class PageManager {
|
|
|
127
120
|
'@_PartName': `/${relativePath}`,
|
|
128
121
|
'@_ContentType': VisioConstants_1.CONTENT_TYPES.VISIO_PAGE
|
|
129
122
|
});
|
|
130
|
-
this.pkg.updateFile(ctPath, this.builder
|
|
123
|
+
this.pkg.updateFile(ctPath, (0, XmlHelper_1.buildXml)(this.builder, parsedCt));
|
|
131
124
|
// 4. Update Relationships (pages.xml -> new page file)
|
|
132
125
|
// Source is "visio/pages/pages.xml", Target is "page{ID}.xml" (relative to source dir)
|
|
133
126
|
const rId = await this.relsManager.ensureRelationship('visio/pages/pages.xml', fileName, VisioConstants_1.RELATIONSHIP_TYPES.PAGE);
|
|
@@ -142,9 +135,10 @@ class PageManager {
|
|
|
142
135
|
parsedPages.Pages.Page.push({
|
|
143
136
|
'@_ID': newId.toString(),
|
|
144
137
|
'@_Name': name,
|
|
138
|
+
'@_NameU': name,
|
|
145
139
|
'Rel': { '@_r:id': rId }
|
|
146
140
|
});
|
|
147
|
-
this.pkg.updateFile(pagesPath, this.builder
|
|
141
|
+
this.pkg.updateFile(pagesPath, (0, XmlHelper_1.buildXml)(this.builder, parsedPages));
|
|
148
142
|
// Reload to include new page
|
|
149
143
|
this.load(true);
|
|
150
144
|
return newId.toString();
|
|
@@ -177,6 +171,7 @@ class PageManager {
|
|
|
177
171
|
<Cell N="PageDrawSizeType" V="0"/>
|
|
178
172
|
</PageSheet>
|
|
179
173
|
<Shapes/>
|
|
174
|
+
<Connects/>
|
|
180
175
|
</PageContents>`;
|
|
181
176
|
this.pkg.updateFile(relativePath, pageContent);
|
|
182
177
|
// 3. Update Content Types
|
|
@@ -191,7 +186,7 @@ class PageManager {
|
|
|
191
186
|
'@_PartName': `/${relativePath}`,
|
|
192
187
|
'@_ContentType': VisioConstants_1.CONTENT_TYPES.VISIO_PAGE
|
|
193
188
|
});
|
|
194
|
-
this.pkg.updateFile(ctPath, this.builder
|
|
189
|
+
this.pkg.updateFile(ctPath, (0, XmlHelper_1.buildXml)(this.builder, parsedCt));
|
|
195
190
|
// 4. Update Relationships
|
|
196
191
|
const rId = await this.relsManager.ensureRelationship('visio/pages/pages.xml', fileName, VisioConstants_1.RELATIONSHIP_TYPES.PAGE);
|
|
197
192
|
// 5. Update Pages Index with Background="true"
|
|
@@ -205,13 +200,77 @@ class PageManager {
|
|
|
205
200
|
parsedPages.Pages.Page.push({
|
|
206
201
|
'@_ID': newId.toString(),
|
|
207
202
|
'@_Name': name,
|
|
203
|
+
'@_NameU': name,
|
|
208
204
|
'@_Background': '1', // Use '1' for boolean true attribute
|
|
209
205
|
'Rel': { '@_r:id': rId }
|
|
210
206
|
});
|
|
211
|
-
this.pkg.updateFile(pagesPath, this.builder
|
|
207
|
+
this.pkg.updateFile(pagesPath, (0, XmlHelper_1.buildXml)(this.builder, parsedPages));
|
|
212
208
|
this.load(true);
|
|
213
209
|
return newId.toString();
|
|
214
210
|
}
|
|
211
|
+
/**
|
|
212
|
+
* Delete a page and clean up all associated XML entries.
|
|
213
|
+
* Removes the page file, its .rels file, the entry in pages.xml,
|
|
214
|
+
* the relationship in pages.xml.rels, the Content Types override,
|
|
215
|
+
* and any BackPage references from other pages that pointed to it.
|
|
216
|
+
*/
|
|
217
|
+
async deletePage(pageId) {
|
|
218
|
+
this.load();
|
|
219
|
+
const page = this.pages.find(p => p.id.toString() === pageId);
|
|
220
|
+
if (!page)
|
|
221
|
+
throw new Error(`Page ${pageId} not found`);
|
|
222
|
+
const pageFileName = page.xmlPath.split('/').pop(); // e.g. "page2.xml"
|
|
223
|
+
// 1. Remove the page XML file
|
|
224
|
+
try {
|
|
225
|
+
this.pkg.removeFile(page.xmlPath);
|
|
226
|
+
}
|
|
227
|
+
catch { /* already gone */ }
|
|
228
|
+
// 2. Remove the page's .rels file if it exists
|
|
229
|
+
const pageRelsPath = `visio/pages/_rels/${pageFileName}.rels`;
|
|
230
|
+
try {
|
|
231
|
+
this.pkg.removeFile(pageRelsPath);
|
|
232
|
+
}
|
|
233
|
+
catch { /* no rels file is fine */ }
|
|
234
|
+
// 3. Remove entry from pages.xml (and strip BackPage refs pointing to this page)
|
|
235
|
+
const pagesPath = 'visio/pages/pages.xml';
|
|
236
|
+
const pagesContent = this.pkg.getFileText(pagesPath);
|
|
237
|
+
const parsedPages = this.parser.parse(pagesContent);
|
|
238
|
+
let pageNodes = parsedPages.Pages.Page;
|
|
239
|
+
if (!Array.isArray(pageNodes))
|
|
240
|
+
pageNodes = pageNodes ? [pageNodes] : [];
|
|
241
|
+
// Remove deleted page and clean up BackPage refs on remaining pages
|
|
242
|
+
parsedPages.Pages.Page = pageNodes
|
|
243
|
+
.filter((n) => n['@_ID'] !== pageId)
|
|
244
|
+
.map((n) => {
|
|
245
|
+
if (n['@_BackPage'] === pageId) {
|
|
246
|
+
const copy = { ...n };
|
|
247
|
+
delete copy['@_BackPage'];
|
|
248
|
+
return copy;
|
|
249
|
+
}
|
|
250
|
+
return n;
|
|
251
|
+
});
|
|
252
|
+
this.pkg.updateFile(pagesPath, (0, XmlHelper_1.buildXml)(this.builder, parsedPages));
|
|
253
|
+
// 4. Remove relationship from pages.xml.rels
|
|
254
|
+
const pagesRelsPath = 'visio/pages/_rels/pages.xml.rels';
|
|
255
|
+
const pagesRelsContent = this.pkg.getFileText(pagesRelsPath);
|
|
256
|
+
const parsedPagesRels = this.parser.parse(pagesRelsContent);
|
|
257
|
+
let rels = parsedPagesRels.Relationships?.Relationship;
|
|
258
|
+
if (!Array.isArray(rels))
|
|
259
|
+
rels = rels ? [rels] : [];
|
|
260
|
+
parsedPagesRels.Relationships.Relationship = rels.filter((r) => r['@_Id'] !== page.relId);
|
|
261
|
+
this.pkg.updateFile(pagesRelsPath, (0, XmlHelper_1.buildXml)(this.builder, parsedPagesRels));
|
|
262
|
+
// 5. Remove Content Types override for the page file
|
|
263
|
+
const ctPath = '[Content_Types].xml';
|
|
264
|
+
const ctContent = this.pkg.getFileText(ctPath);
|
|
265
|
+
const parsedCt = this.parser.parse(ctContent);
|
|
266
|
+
let overrides = parsedCt.Types.Override;
|
|
267
|
+
if (!Array.isArray(overrides))
|
|
268
|
+
overrides = overrides ? [overrides] : [];
|
|
269
|
+
parsedCt.Types.Override = overrides.filter((o) => !o['@_PartName']?.endsWith(pageFileName));
|
|
270
|
+
this.pkg.updateFile(ctPath, (0, XmlHelper_1.buildXml)(this.builder, parsedCt));
|
|
271
|
+
// 6. Reload the page list to reflect the deletion
|
|
272
|
+
this.load(true);
|
|
273
|
+
}
|
|
215
274
|
/**
|
|
216
275
|
* Set a background page for a foreground page
|
|
217
276
|
*/
|
|
@@ -237,7 +296,7 @@ class PageManager {
|
|
|
237
296
|
if (fgNode) {
|
|
238
297
|
fgNode['@_BackPage'] = backgroundPageId.toString();
|
|
239
298
|
}
|
|
240
|
-
this.pkg.updateFile(pagesPath, this.builder
|
|
299
|
+
this.pkg.updateFile(pagesPath, (0, XmlHelper_1.buildXml)(this.builder, parsedPages));
|
|
241
300
|
this.load(true);
|
|
242
301
|
}
|
|
243
302
|
}
|
package/dist/core/RelsManager.js
CHANGED
|
@@ -1,20 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.RelsManager = void 0;
|
|
4
|
-
const fast_xml_parser_1 = require("fast-xml-parser");
|
|
5
4
|
const VisioConstants_1 = require("./VisioConstants");
|
|
5
|
+
const XmlHelper_1 = require("../utils/XmlHelper");
|
|
6
6
|
class RelsManager {
|
|
7
7
|
constructor(pkg) {
|
|
8
8
|
this.pkg = pkg;
|
|
9
|
-
this.parser =
|
|
10
|
-
|
|
11
|
-
attributeNamePrefix: "@_"
|
|
12
|
-
});
|
|
13
|
-
this.builder = new fast_xml_parser_1.XMLBuilder({
|
|
14
|
-
ignoreAttributes: false,
|
|
15
|
-
attributeNamePrefix: "@_",
|
|
16
|
-
format: true
|
|
17
|
-
});
|
|
9
|
+
this.parser = (0, XmlHelper_1.createXmlParser)();
|
|
10
|
+
this.builder = (0, XmlHelper_1.createXmlBuilder)();
|
|
18
11
|
}
|
|
19
12
|
getRelsPath(partPath) {
|
|
20
13
|
// file.xml -> _rels/file.xml.rels
|
|
@@ -66,7 +59,7 @@ class RelsManager {
|
|
|
66
59
|
'@_Target': target
|
|
67
60
|
});
|
|
68
61
|
// Save back
|
|
69
|
-
const newXml = this.builder
|
|
62
|
+
const newXml = (0, XmlHelper_1.buildXml)(this.builder, parsed);
|
|
70
63
|
this.pkg.updateFile(relsPath, newXml);
|
|
71
64
|
return newId;
|
|
72
65
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
export { VisioPackage } from './VisioPackage';
|
|
2
|
-
export { PageManager } from './PageManager';
|
|
2
|
+
export { PageManager } from './core/PageManager';
|
|
3
|
+
export type { PageEntry } from './core/PageManager';
|
|
3
4
|
export { ShapeReader } from './ShapeReader';
|
|
4
5
|
export { ShapeModifier } from './ShapeModifier';
|
|
5
6
|
export { VisioDocument } from './VisioDocument';
|
|
6
7
|
export { Page } from './Page';
|
|
7
8
|
export { Shape } from './Shape';
|
|
9
|
+
export type { ShapeData, ShapeHyperlink } from './Shape';
|
|
8
10
|
export * from './types/VisioTypes';
|
package/dist/index.js
CHANGED
|
@@ -17,7 +17,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
17
17
|
exports.Shape = exports.Page = exports.VisioDocument = exports.ShapeModifier = exports.ShapeReader = exports.PageManager = exports.VisioPackage = void 0;
|
|
18
18
|
var VisioPackage_1 = require("./VisioPackage");
|
|
19
19
|
Object.defineProperty(exports, "VisioPackage", { enumerable: true, get: function () { return VisioPackage_1.VisioPackage; } });
|
|
20
|
-
var PageManager_1 = require("./PageManager");
|
|
20
|
+
var PageManager_1 = require("./core/PageManager");
|
|
21
21
|
Object.defineProperty(exports, "PageManager", { enumerable: true, get: function () { return PageManager_1.PageManager; } });
|
|
22
22
|
var ShapeReader_1 = require("./ShapeReader");
|
|
23
23
|
Object.defineProperty(exports, "ShapeReader", { enumerable: true, get: function () { return ShapeReader_1.ShapeReader; } });
|
|
@@ -55,9 +55,8 @@ class ConnectorBuilder {
|
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
57
|
};
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
: [];
|
|
58
|
+
const rawShapes = parsed.PageContents.Shapes?.Shape;
|
|
59
|
+
const topShapes = Array.isArray(rawShapes) ? rawShapes : rawShapes ? [rawShapes] : [];
|
|
61
60
|
mapHierarchy(topShapes, null);
|
|
62
61
|
return shapeHierarchy;
|
|
63
62
|
}
|
|
@@ -120,16 +119,16 @@ class ConnectorBuilder {
|
|
|
120
119
|
{ '@_N': 'ShapePermeableX', '@_V': '0' },
|
|
121
120
|
{ '@_N': 'ShapePermeableY', '@_V': '0' },
|
|
122
121
|
{ '@_N': 'ShapeRouteStyle', '@_V': '1' },
|
|
123
|
-
{ '@_N': 'ConFixedCode', '@_V': '0' }
|
|
122
|
+
{ '@_N': 'ConFixedCode', '@_V': '0' },
|
|
123
|
+
{ '@_N': 'BeginArrow', '@_V': beginArrow || '0' },
|
|
124
|
+
{ '@_N': 'BeginArrowSize', '@_V': '2' },
|
|
125
|
+
{ '@_N': 'EndArrow', '@_V': endArrow || '0' },
|
|
126
|
+
{ '@_N': 'EndArrowSize', '@_V': '2' }
|
|
124
127
|
],
|
|
125
128
|
Section: [
|
|
126
129
|
(0, StyleHelpers_1.createLineSection)({
|
|
127
130
|
color: '#000000',
|
|
128
|
-
weight: '0.01'
|
|
129
|
-
beginArrow: beginArrow || '0',
|
|
130
|
-
beginArrowSize: '2',
|
|
131
|
-
endArrow: endArrow || '0',
|
|
132
|
-
endArrowSize: '2'
|
|
131
|
+
weight: '0.01'
|
|
133
132
|
}),
|
|
134
133
|
{
|
|
135
134
|
'@_N': 'Geometry',
|
|
@@ -64,18 +64,20 @@ class ContainerBuilder {
|
|
|
64
64
|
textXform.Cell = [];
|
|
65
65
|
if (!Array.isArray(textXform.Cell))
|
|
66
66
|
textXform.Cell = [textXform.Cell];
|
|
67
|
-
|
|
67
|
+
// Extract the shape's current height for use as the static @_V on TxtPinY.
|
|
68
|
+
const heightVal = shape.Cell?.find((c) => c['@_N'] === 'Height')?.['@_V'] ?? '1';
|
|
69
|
+
const upsertCell = (name, formula, unit, val) => {
|
|
68
70
|
const idx = textXform.Cell.findIndex((c) => c['@_N'] === name);
|
|
69
|
-
const cell = { '@_N': name, '@_V': val, '@_U': unit };
|
|
71
|
+
const cell = { '@_N': name, '@_V': val, '@_F': formula, '@_U': unit };
|
|
70
72
|
if (idx >= 0)
|
|
71
73
|
textXform.Cell[idx] = cell;
|
|
72
74
|
else
|
|
73
75
|
textXform.Cell.push(cell);
|
|
74
76
|
};
|
|
75
|
-
// Move text to top of shape:
|
|
76
|
-
//
|
|
77
|
-
upsertCell('TxtPinY', 'Height', 'DY');
|
|
78
|
-
upsertCell('TxtLocPinY', 'TxtHeight', 'DY');
|
|
77
|
+
// Move text to top of shape: formula drives dynamic sizing; @_V is the static initial value.
|
|
78
|
+
// TxtHeight is Visio-computed, so its static value is left as '0'.
|
|
79
|
+
upsertCell('TxtPinY', 'Height', 'DY', heightVal);
|
|
80
|
+
upsertCell('TxtLocPinY', 'TxtHeight', 'DY', '0');
|
|
79
81
|
}
|
|
80
82
|
static makeList(shape, direction = 'vertical') {
|
|
81
83
|
// 1. Convert basic container to List
|
|
@@ -8,14 +8,17 @@ class ForeignShapeBuilder {
|
|
|
8
8
|
'@_NameU': `Sheet.${id}`,
|
|
9
9
|
'@_Name': `Sheet.${id}`,
|
|
10
10
|
'@_Type': 'Foreign',
|
|
11
|
-
ForeignData: {
|
|
11
|
+
ForeignData: {
|
|
12
|
+
'@_ForeignType': 'Bitmap',
|
|
13
|
+
Rel: { '@_r:id': rId }
|
|
14
|
+
},
|
|
12
15
|
Cell: [
|
|
13
16
|
{ '@_N': 'PinX', '@_V': props.x.toString() },
|
|
14
17
|
{ '@_N': 'PinY', '@_V': props.y.toString() },
|
|
15
18
|
{ '@_N': 'Width', '@_V': props.width.toString() },
|
|
16
19
|
{ '@_N': 'Height', '@_V': props.height.toString() },
|
|
17
|
-
{ '@_N': 'LocPinX', '@_V': (props.width / 2).toString() },
|
|
18
|
-
{ '@_N': 'LocPinY', '@_V': (props.height / 2).toString() }
|
|
20
|
+
{ '@_N': 'LocPinX', '@_V': (props.width / 2).toString(), '@_F': 'Width*0.5' },
|
|
21
|
+
{ '@_N': 'LocPinY', '@_V': (props.height / 2).toString(), '@_F': 'Height*0.5' }
|
|
19
22
|
],
|
|
20
23
|
Section: [
|
|
21
24
|
// Foreign shapes typically have no border (LinePattern=0)
|
|
@@ -34,9 +37,9 @@ class ForeignShapeBuilder {
|
|
|
34
37
|
Cell: [{ '@_N': 'NoFill', '@_V': '1' }], // Images usually don't have a fill behind them
|
|
35
38
|
Row: [
|
|
36
39
|
{ '@_T': 'MoveTo', '@_IX': '1', Cell: [{ '@_N': 'X', '@_V': '0' }, { '@_N': 'Y', '@_V': '0' }] },
|
|
37
|
-
{ '@_T': 'LineTo', '@_IX': '2', Cell: [{ '@_N': 'X', '@_V': props.width.toString() }, { '@_N': 'Y', '@_V': '0' }] },
|
|
38
|
-
{ '@_T': 'LineTo', '@_IX': '3', Cell: [{ '@_N': 'X', '@_V': props.width.toString() }, { '@_N': 'Y', '@_V': props.height.toString() }] },
|
|
39
|
-
{ '@_T': 'LineTo', '@_IX': '4', Cell: [{ '@_N': 'X', '@_V': '0' }, { '@_N': 'Y', '@_V': props.height.toString() }] },
|
|
40
|
+
{ '@_T': 'LineTo', '@_IX': '2', Cell: [{ '@_N': 'X', '@_V': props.width.toString(), '@_F': 'Width' }, { '@_N': 'Y', '@_V': '0' }] },
|
|
41
|
+
{ '@_T': 'LineTo', '@_IX': '3', Cell: [{ '@_N': 'X', '@_V': props.width.toString(), '@_F': 'Width' }, { '@_N': 'Y', '@_V': props.height.toString(), '@_F': 'Height' }] },
|
|
42
|
+
{ '@_T': 'LineTo', '@_IX': '4', Cell: [{ '@_N': 'X', '@_V': '0' }, { '@_N': 'Y', '@_V': props.height.toString(), '@_F': 'Height' }] },
|
|
40
43
|
{ '@_T': 'LineTo', '@_IX': '5', Cell: [{ '@_N': 'X', '@_V': '0' }, { '@_N': 'Y', '@_V': '0' }] }
|
|
41
44
|
]
|
|
42
45
|
}
|