ts-visio 1.3.0 → 1.4.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 CHANGED
@@ -24,6 +24,7 @@ Built using specific schema-level abstractions to handle the complex internal st
24
24
  - **Lookup API**: Find shapes by ID, predicate, or look up pages by name.
25
25
  - **Read-Back API**: Read custom properties, hyperlinks, and layer assignments from existing shapes.
26
26
  - **Page Size & Orientation**: Set canvas dimensions with named sizes (`Letter`, `A4`, …) or raw inches; rotate between portrait and landscape.
27
+ - **Document Metadata**: Read and write document properties (title, author, description, keywords, company, dates) via `doc.getMetadata()` / `doc.setMetadata()`.
27
28
 
28
29
  Feature gaps are being tracked in [FEATURES.md](./FEATURES.md).
29
30
 
@@ -490,6 +491,36 @@ Available named sizes in `PageSizes`: `Letter`, `Legal`, `Tabloid`, `A3`, `A4`,
490
491
 
491
492
  ---
492
493
 
494
+ #### 26. Document Metadata
495
+ Set and read document-level properties that appear in Visio's Document Properties dialog.
496
+
497
+ ```typescript
498
+ // Write metadata (only supplied fields are changed)
499
+ doc.setMetadata({
500
+ title: 'Network Topology',
501
+ author: 'Alice',
502
+ description: 'Data-centre interconnect diagram',
503
+ keywords: 'network datacenter cloud',
504
+ lastModifiedBy: 'CI pipeline',
505
+ company: 'ACME Corp',
506
+ manager: 'Bob',
507
+ created: new Date('2025-01-01T00:00:00Z'),
508
+ modified: new Date(),
509
+ });
510
+
511
+ // Read back all metadata fields
512
+ const meta = doc.getMetadata();
513
+ console.log(meta.title); // 'Network Topology'
514
+ console.log(meta.author); // 'Alice'
515
+ console.log(meta.company); // 'ACME Corp'
516
+ console.log(meta.created); // Date object
517
+ ```
518
+
519
+ Fields map to OPC parts: `title`, `author`, `description`, `keywords`, `lastModifiedBy`,
520
+ `created`, `modified` → `docProps/core.xml`; `company`, `manager` → `docProps/app.xml`.
521
+
522
+ ---
523
+
493
524
  ## Examples
494
525
 
495
526
  Check out the [examples](./examples) directory for complete scripts.
@@ -1,9 +1,11 @@
1
1
  import { Page } from './Page';
2
+ import { DocumentMetadata } from './types/VisioTypes';
2
3
  export declare class VisioDocument {
3
4
  private pkg;
4
5
  private pageManager;
5
6
  private _pageCache;
6
7
  private mediaManager;
8
+ private metadataManager;
7
9
  private constructor();
8
10
  static create(): Promise<VisioDocument>;
9
11
  static load(pathOrBuffer: string | Buffer | ArrayBuffer | Uint8Array): Promise<VisioDocument>;
@@ -27,5 +29,18 @@ export declare class VisioDocument {
27
29
  * and any BackPage references from other pages.
28
30
  */
29
31
  deletePage(page: Page): Promise<void>;
32
+ /**
33
+ * Read document metadata from `docProps/core.xml` and `docProps/app.xml`.
34
+ * Fields not present in the file are returned as `undefined`.
35
+ */
36
+ getMetadata(): DocumentMetadata;
37
+ /**
38
+ * Write document metadata. Only the supplied fields are changed;
39
+ * all other fields keep their existing values.
40
+ *
41
+ * @example
42
+ * doc.setMetadata({ title: 'My Diagram', author: 'Alice', company: 'ACME' });
43
+ */
44
+ setMetadata(props: Partial<DocumentMetadata>): void;
30
45
  save(filename?: string): Promise<Buffer>;
31
46
  }
@@ -38,12 +38,14 @@ const VisioPackage_1 = require("./VisioPackage");
38
38
  const PageManager_1 = require("./core/PageManager");
39
39
  const Page_1 = require("./Page");
40
40
  const MediaManager_1 = require("./core/MediaManager");
41
+ const MetadataManager_1 = require("./core/MetadataManager");
41
42
  class VisioDocument {
42
43
  constructor(pkg) {
43
44
  this.pkg = pkg;
44
45
  this._pageCache = null;
45
46
  this.pageManager = new PageManager_1.PageManager(pkg);
46
47
  this.mediaManager = new MediaManager_1.MediaManager(pkg);
48
+ this.metadataManager = new MetadataManager_1.MetadataManager(pkg);
47
49
  }
48
50
  static async create() {
49
51
  const pkg = await VisioPackage_1.VisioPackage.create();
@@ -131,6 +133,23 @@ class VisioDocument {
131
133
  await this.pageManager.deletePage(page.id);
132
134
  this._pageCache = null;
133
135
  }
136
+ /**
137
+ * Read document metadata from `docProps/core.xml` and `docProps/app.xml`.
138
+ * Fields not present in the file are returned as `undefined`.
139
+ */
140
+ getMetadata() {
141
+ return this.metadataManager.getMetadata();
142
+ }
143
+ /**
144
+ * Write document metadata. Only the supplied fields are changed;
145
+ * all other fields keep their existing values.
146
+ *
147
+ * @example
148
+ * doc.setMetadata({ title: 'My Diagram', author: 'Alice', company: 'ACME' });
149
+ */
150
+ setMetadata(props) {
151
+ this.metadataManager.setMetadata(props);
152
+ }
134
153
  async save(filename) {
135
154
  return this.pkg.save(filename);
136
155
  }
@@ -0,0 +1,22 @@
1
+ import { VisioPackage } from '../VisioPackage';
2
+ import { DocumentMetadata } from '../types/VisioTypes';
3
+ export declare class MetadataManager {
4
+ private pkg;
5
+ private parser;
6
+ private builder;
7
+ constructor(pkg: VisioPackage);
8
+ /** Read document metadata from `docProps/core.xml` and `docProps/app.xml`. */
9
+ getMetadata(): DocumentMetadata;
10
+ /** Merge the supplied fields into the existing metadata and persist to the package. */
11
+ setMetadata(props: Partial<DocumentMetadata>): void;
12
+ /** Extract a string from a parsed XML node (handles plain strings and #text objects). */
13
+ private str;
14
+ /** Parse an ISO datetime string into a Date (returns undefined on failure). */
15
+ private date;
16
+ private parsedCore;
17
+ private parsedApp;
18
+ private writeCore;
19
+ private writeApp;
20
+ private blankCoreParsed;
21
+ private blankAppParsed;
22
+ }
@@ -0,0 +1,154 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MetadataManager = void 0;
4
+ const VisioConstants_1 = require("./VisioConstants");
5
+ const XmlHelper_1 = require("../utils/XmlHelper");
6
+ class MetadataManager {
7
+ constructor(pkg) {
8
+ this.pkg = pkg;
9
+ this.parser = (0, XmlHelper_1.createXmlParser)();
10
+ this.builder = (0, XmlHelper_1.createXmlBuilder)();
11
+ }
12
+ // ---- public API --------------------------------------------------------
13
+ /** Read document metadata from `docProps/core.xml` and `docProps/app.xml`. */
14
+ getMetadata() {
15
+ const core = this.parsedCore();
16
+ const app = this.parsedApp();
17
+ return {
18
+ title: this.str(core['dc:title']),
19
+ author: this.str(core['dc:creator']),
20
+ description: this.str(core['dc:description']),
21
+ keywords: this.str(core['cp:keywords']),
22
+ lastModifiedBy: this.str(core['cp:lastModifiedBy']),
23
+ created: this.date(core['dcterms:created']),
24
+ modified: this.date(core['dcterms:modified']),
25
+ company: this.str(app['Company']),
26
+ manager: this.str(app['Manager']),
27
+ };
28
+ }
29
+ /** Merge the supplied fields into the existing metadata and persist to the package. */
30
+ setMetadata(props) {
31
+ const coreKeys = [
32
+ 'title', 'author', 'description', 'keywords', 'lastModifiedBy', 'created', 'modified',
33
+ ];
34
+ const appKeys = ['company', 'manager'];
35
+ if (coreKeys.some(k => k in props))
36
+ this.writeCore(props);
37
+ if (appKeys.some(k => k in props))
38
+ this.writeApp(props);
39
+ }
40
+ // ---- private helpers ---------------------------------------------------
41
+ /** Extract a string from a parsed XML node (handles plain strings and #text objects). */
42
+ str(val) {
43
+ if (val === undefined || val === null)
44
+ return undefined;
45
+ if (typeof val === 'string')
46
+ return val || undefined;
47
+ if (typeof val === 'object' && '#text' in val) {
48
+ const t = val['#text'];
49
+ const s = typeof t === 'string' ? t : String(t);
50
+ return s || undefined;
51
+ }
52
+ return undefined;
53
+ }
54
+ /** Parse an ISO datetime string into a Date (returns undefined on failure). */
55
+ date(val) {
56
+ const text = this.str(val);
57
+ if (!text)
58
+ return undefined;
59
+ const d = new Date(text);
60
+ return isNaN(d.getTime()) ? undefined : d;
61
+ }
62
+ parsedCore() {
63
+ try {
64
+ const xml = this.pkg.getFileText('docProps/core.xml');
65
+ const parsed = this.parser.parse(xml);
66
+ return parsed['cp:coreProperties'] ?? {};
67
+ }
68
+ catch {
69
+ return {};
70
+ }
71
+ }
72
+ parsedApp() {
73
+ try {
74
+ const xml = this.pkg.getFileText('docProps/app.xml');
75
+ const parsed = this.parser.parse(xml);
76
+ return parsed['Properties'] ?? {};
77
+ }
78
+ catch {
79
+ return {};
80
+ }
81
+ }
82
+ writeCore(props) {
83
+ let parsed;
84
+ try {
85
+ const xml = this.pkg.getFileText('docProps/core.xml');
86
+ parsed = this.parser.parse(xml);
87
+ }
88
+ catch {
89
+ parsed = this.blankCoreParsed();
90
+ }
91
+ const root = parsed['cp:coreProperties'];
92
+ if ('title' in props)
93
+ root['dc:title'] = props.title ?? '';
94
+ if ('author' in props)
95
+ root['dc:creator'] = props.author ?? '';
96
+ if ('description' in props)
97
+ root['dc:description'] = props.description ?? '';
98
+ if ('keywords' in props)
99
+ root['cp:keywords'] = props.keywords ?? '';
100
+ if ('lastModifiedBy' in props)
101
+ root['cp:lastModifiedBy'] = props.lastModifiedBy ?? '';
102
+ if ('created' in props && props.created !== undefined) {
103
+ root['dcterms:created'] = {
104
+ '@_xsi:type': 'dcterms:W3CDTF',
105
+ '#text': props.created.toISOString(),
106
+ };
107
+ }
108
+ if ('modified' in props && props.modified !== undefined) {
109
+ root['dcterms:modified'] = {
110
+ '@_xsi:type': 'dcterms:W3CDTF',
111
+ '#text': props.modified.toISOString(),
112
+ };
113
+ }
114
+ this.pkg.updateFile('docProps/core.xml', (0, XmlHelper_1.buildXml)(this.builder, parsed));
115
+ }
116
+ writeApp(props) {
117
+ let parsed;
118
+ try {
119
+ const xml = this.pkg.getFileText('docProps/app.xml');
120
+ parsed = this.parser.parse(xml);
121
+ }
122
+ catch {
123
+ parsed = this.blankAppParsed();
124
+ }
125
+ const root = parsed['Properties'];
126
+ if ('company' in props)
127
+ root['Company'] = props.company ?? '';
128
+ if ('manager' in props)
129
+ root['Manager'] = props.manager ?? '';
130
+ this.pkg.updateFile('docProps/app.xml', (0, XmlHelper_1.buildXml)(this.builder, parsed));
131
+ }
132
+ blankCoreParsed() {
133
+ return {
134
+ 'cp:coreProperties': {
135
+ '@_xmlns:cp': VisioConstants_1.XML_NAMESPACES.CORE_PROPERTIES,
136
+ '@_xmlns:dc': VisioConstants_1.XML_NAMESPACES.DC_ELEMENTS,
137
+ '@_xmlns:dcterms': VisioConstants_1.XML_NAMESPACES.DC_TERMS,
138
+ '@_xmlns:dcmitype': VisioConstants_1.XML_NAMESPACES.DC_DCMITYPE,
139
+ '@_xmlns:xsi': VisioConstants_1.XML_NAMESPACES.XSI,
140
+ },
141
+ };
142
+ }
143
+ blankAppParsed() {
144
+ return {
145
+ 'Properties': {
146
+ '@_xmlns': VisioConstants_1.XML_NAMESPACES.EXTENDED_PROPERTIES,
147
+ '@_xmlns:vt': VisioConstants_1.XML_NAMESPACES.DOC_PROPS_VTYPES,
148
+ 'Application': 'ts-visio',
149
+ 'Template': 'Basic',
150
+ },
151
+ };
152
+ }
153
+ }
154
+ exports.MetadataManager = MetadataManager;
@@ -72,6 +72,27 @@ export declare enum VisioPropType {
72
72
  Duration = 6,
73
73
  Currency = 7
74
74
  }
75
+ /** Document-level metadata that maps to `docProps/core.xml` and `docProps/app.xml`. */
76
+ export interface DocumentMetadata {
77
+ /** Document title (`dc:title`). */
78
+ title?: string;
79
+ /** Author / creator (`dc:creator`). */
80
+ author?: string;
81
+ /** Short description (`dc:description`). */
82
+ description?: string;
83
+ /** Space-separated keywords (`cp:keywords`). */
84
+ keywords?: string;
85
+ /** Last-modified-by user (`cp:lastModifiedBy`). */
86
+ lastModifiedBy?: string;
87
+ /** Company name from `app.xml` `<Company>`. */
88
+ company?: string;
89
+ /** Manager name from `app.xml` `<Manager>`. */
90
+ manager?: string;
91
+ /** Document creation timestamp (`dcterms:created`). */
92
+ created?: Date;
93
+ /** Last-modified timestamp (`dcterms:modified`). */
94
+ modified?: Date;
95
+ }
75
96
  export type PageOrientation = 'portrait' | 'landscape';
76
97
  /** Common paper sizes in inches (width × height in portrait orientation). */
77
98
  export declare const PageSizes: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ts-visio",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "scripts": {