@docx4j/core-ts 0.1.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.
Files changed (92) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +12 -0
  3. package/README.md +126 -0
  4. package/dist/index.d.mts +7 -0
  5. package/dist/index.mjs +10 -0
  6. package/dist/model/content/Body.d.mts +107 -0
  7. package/dist/model/content/Body.mjs +363 -0
  8. package/dist/model/content/Font.d.mts +46 -0
  9. package/dist/model/content/Font.mjs +50 -0
  10. package/dist/model/content/Paragraph.d.mts +99 -0
  11. package/dist/model/content/Paragraph.mjs +364 -0
  12. package/dist/model/content/Range.d.mts +34 -0
  13. package/dist/model/content/Range.mjs +94 -0
  14. package/dist/model/content/index.d.mts +7 -0
  15. package/dist/model/content/index.mjs +9 -0
  16. package/dist/model/content/search.d.mts +12 -0
  17. package/dist/model/content/search.mjs +68 -0
  18. package/dist/model/content/tree.d.mts +40 -0
  19. package/dist/model/content/tree.mjs +120 -0
  20. package/dist/model/index.d.mts +1 -0
  21. package/dist/model/index.mjs +2 -0
  22. package/dist/opc/ContentTypeManager.d.mts +37 -0
  23. package/dist/opc/ContentTypeManager.mjs +126 -0
  24. package/dist/opc/ContentTypes.d.mts +118 -0
  25. package/dist/opc/ContentTypes.mjs +139 -0
  26. package/dist/opc/FlatOpcPartStore.d.mts +42 -0
  27. package/dist/opc/FlatOpcPartStore.mjs +151 -0
  28. package/dist/opc/Load.d.mts +18 -0
  29. package/dist/opc/Load.mjs +123 -0
  30. package/dist/opc/PartName.d.mts +46 -0
  31. package/dist/opc/PartName.mjs +132 -0
  32. package/dist/opc/PartStore.d.mts +45 -0
  33. package/dist/opc/PartStore.mjs +54 -0
  34. package/dist/opc/Save.d.mts +9 -0
  35. package/dist/opc/Save.mjs +52 -0
  36. package/dist/opc/ZipPartStore.d.mts +32 -0
  37. package/dist/opc/ZipPartStore.mjs +117 -0
  38. package/dist/opc/exceptions.d.mts +17 -0
  39. package/dist/opc/exceptions.mjs +22 -0
  40. package/dist/opc/index.d.mts +12 -0
  41. package/dist/opc/index.mjs +13 -0
  42. package/dist/opc/mce/preprocessor.d.mts +15 -0
  43. package/dist/opc/mce/preprocessor.mjs +74 -0
  44. package/dist/opc/mce/understood.d.mts +1 -0
  45. package/dist/opc/mce/understood.mjs +98 -0
  46. package/dist/packages/OpcPackage.d.mts +62 -0
  47. package/dist/packages/OpcPackage.mjs +133 -0
  48. package/dist/packages/PresentationMLPackage.d.mts +12 -0
  49. package/dist/packages/PresentationMLPackage.mjs +39 -0
  50. package/dist/packages/SpreadsheetMLPackage.d.mts +12 -0
  51. package/dist/packages/SpreadsheetMLPackage.mjs +38 -0
  52. package/dist/packages/WordprocessingMLPackage.d.mts +49 -0
  53. package/dist/packages/WordprocessingMLPackage.mjs +130 -0
  54. package/dist/packages/index.d.mts +5 -0
  55. package/dist/packages/index.mjs +6 -0
  56. package/dist/packages/registry.d.mts +7 -0
  57. package/dist/packages/registry.mjs +18 -0
  58. package/dist/parts/BinaryPart.d.mts +38 -0
  59. package/dist/parts/BinaryPart.mjs +72 -0
  60. package/dist/parts/DefaultXmlPart.d.mts +29 -0
  61. package/dist/parts/DefaultXmlPart.mjs +63 -0
  62. package/dist/parts/Namespaces.d.mts +99 -0
  63. package/dist/parts/Namespaces.mjs +99 -0
  64. package/dist/parts/Part.d.mts +50 -0
  65. package/dist/parts/Part.mjs +68 -0
  66. package/dist/parts/PartRegistry.d.mts +20 -0
  67. package/dist/parts/PartRegistry.mjs +142 -0
  68. package/dist/parts/Parts.d.mts +13 -0
  69. package/dist/parts/Parts.mjs +28 -0
  70. package/dist/parts/RelationshipsPart.d.mts +66 -0
  71. package/dist/parts/RelationshipsPart.mjs +224 -0
  72. package/dist/parts/XmlPart.d.mts +46 -0
  73. package/dist/parts/XmlPart.mjs +87 -0
  74. package/dist/parts/customXml/index.d.mts +9 -0
  75. package/dist/parts/customXml/index.mjs +11 -0
  76. package/dist/parts/dml/index.d.mts +54 -0
  77. package/dist/parts/dml/index.mjs +83 -0
  78. package/dist/parts/docProps/index.d.mts +14 -0
  79. package/dist/parts/docProps/index.mjs +18 -0
  80. package/dist/parts/index.d.mts +14 -0
  81. package/dist/parts/index.mjs +15 -0
  82. package/dist/parts/pml/index.d.mts +53 -0
  83. package/dist/parts/pml/index.mjs +90 -0
  84. package/dist/parts/sml/index.d.mts +61 -0
  85. package/dist/parts/sml/index.mjs +107 -0
  86. package/dist/parts/wml/defaultStyles.d.mts +1 -0
  87. package/dist/parts/wml/defaultStyles.mjs +500 -0
  88. package/dist/parts/wml/index.d.mts +102 -0
  89. package/dist/parts/wml/index.mjs +213 -0
  90. package/dist/xml/dom.d.mts +22 -0
  91. package/dist/xml/dom.mjs +92 -0
  92. package/package.json +75 -0
@@ -0,0 +1,151 @@
1
+ import { marshalNode } from '@docx4j/generated-objects-ts';
2
+ import { ContentTypeManager } from './ContentTypeManager.mjs';
3
+ import { ContentTypes, isXmlContentType } from './ContentTypes.mjs';
4
+ import { CONTENT_TYPES_NAME } from './PartName.mjs';
5
+ import { InvalidFormatException } from './exceptions.mjs';
6
+ import { parseXml, serializeXml, childElements, encodeText, decodeXmlText, stripXmlDeclaration, base64Decode, base64Encode, XML_DECLARATION } from '../xml/dom.mjs';
7
+ export const PKG_NS = 'http://schemas.microsoft.com/office/2006/xmlPackage';
8
+ /**
9
+ * A flat OPC package (`pkg:package`, what Office JS `getOoxml()` returns) as a container
10
+ * (docx4j org.docx4j.openpackaging.io3.stores.FlatOpcPartStore is not a class there, but the
11
+ * same job is done by `FlatOpcXmlImporter`). The content types are carried per part, so
12
+ * `[Content_Types].xml` is synthesised. XML parts are serialised back to bytes on `load`; the
13
+ * document loaded from them is therefore not byte-identical to what Office would have zipped.
14
+ */
15
+ export class FlatOpcPartStore {
16
+ /** From the `pkg:package` string. */
17
+ constructor(source) {
18
+ this.parts = new Map();
19
+ this.order = [];
20
+ this.cache = new Map();
21
+ if (typeof source === 'string')
22
+ this.readString(source);
23
+ else
24
+ this.readPackageElement(source);
25
+ this.order.unshift(CONTENT_TYPES_NAME);
26
+ }
27
+ readString(text) {
28
+ var _a;
29
+ const doc = parseXml(text);
30
+ const root = doc.documentElement;
31
+ if (root.namespaceURI !== PKG_NS || root.localName !== 'package') {
32
+ throw new InvalidFormatException(`Not a flat OPC package: root is ${root.nodeName}`);
33
+ }
34
+ for (const part of childElements(root)) {
35
+ if (part.namespaceURI !== PKG_NS || part.localName !== 'part')
36
+ continue;
37
+ const name = part.getAttributeNS(PKG_NS, 'name') || part.getAttribute('pkg:name');
38
+ const contentType = part.getAttributeNS(PKG_NS, 'contentType') || part.getAttribute('pkg:contentType');
39
+ if (!name || !contentType)
40
+ throw new InvalidFormatException('pkg:part without pkg:name or pkg:contentType');
41
+ const entry = { name: name.replace(/^\//, ''), contentType };
42
+ for (const child of childElements(part)) {
43
+ if (child.namespaceURI !== PKG_NS)
44
+ continue;
45
+ if (child.localName === 'xmlData')
46
+ entry.xml = childElements(child)[0];
47
+ else if (child.localName === 'binaryData')
48
+ entry.binary = (_a = child.textContent) !== null && _a !== void 0 ? _a : '';
49
+ }
50
+ this.add(entry);
51
+ }
52
+ }
53
+ readPackageElement(pkg) {
54
+ var _a, _b, _c, _d;
55
+ for (const part of (_a = pkg.value.part) !== null && _a !== void 0 ? _a : []) {
56
+ const name = ((_b = part.name) !== null && _b !== void 0 ? _b : '').replace(/^\//, '');
57
+ const contentType = (_c = part.contentType) !== null && _c !== void 0 ? _c : '';
58
+ const entry = { name, contentType };
59
+ const any = (_d = part.xmlData) === null || _d === void 0 ? void 0 : _d.any;
60
+ if (any !== undefined && any !== null)
61
+ entry.xml = any;
62
+ if (part.binaryData !== undefined && part.binaryData !== null)
63
+ entry.binary = String(part.binaryData);
64
+ this.add(entry);
65
+ }
66
+ }
67
+ add(entry) {
68
+ this.parts.set(entry.name, entry);
69
+ this.order.push(entry.name);
70
+ }
71
+ /** The content type of a part as carried by the flat package. */
72
+ contentTypeOf(partName) {
73
+ var _a;
74
+ return (_a = this.parts.get(partName)) === null || _a === void 0 ? void 0 : _a.contentType;
75
+ }
76
+ partNames() {
77
+ return this.order;
78
+ }
79
+ has(partName) {
80
+ return partName === CONTENT_TYPES_NAME || this.parts.has(partName);
81
+ }
82
+ async load(partName) {
83
+ var _a;
84
+ if (partName === CONTENT_TYPES_NAME)
85
+ return ((_a = this.contentTypesXml) !== null && _a !== void 0 ? _a : (this.contentTypesXml = encodeText(this.buildContentTypes().toXml())));
86
+ const cached = this.cache.get(partName);
87
+ if (cached)
88
+ return cached;
89
+ const part = this.parts.get(partName);
90
+ if (!part)
91
+ throw new Error(`No part ${partName} in flat OPC package`);
92
+ let bytes;
93
+ if (part.binary !== undefined) {
94
+ bytes = base64Decode(part.binary);
95
+ }
96
+ else if (part.xml !== undefined) {
97
+ const el = isNode(part.xml) ? part.xml : await marshalNode(part.xml);
98
+ bytes = encodeText(XML_DECLARATION + serializeXml(el));
99
+ }
100
+ else {
101
+ bytes = new Uint8Array(0);
102
+ }
103
+ this.cache.set(partName, bytes);
104
+ return bytes;
105
+ }
106
+ buildContentTypes() {
107
+ const ctm = ContentTypeManager.createDefault();
108
+ for (const part of this.parts.values()) {
109
+ if (part.contentType === ContentTypes.RELATIONSHIPS_PART)
110
+ continue;
111
+ ctm.addContentType('/' + part.name, part.contentType);
112
+ }
113
+ return ctm;
114
+ }
115
+ }
116
+ function isNode(value) {
117
+ return typeof value === 'object' && value !== null && typeof value.nodeType === 'number';
118
+ }
119
+ /**
120
+ * Produces the `pkg:package` string for Office JS `insertOoxml()`. XML parts are embedded as
121
+ * XML (their declaration removed), everything else as base64 with the padding Word writes.
122
+ */
123
+ export class FlatOpcPartSink {
124
+ constructor(options) {
125
+ this.parts = [];
126
+ this.progId = options === null || options === void 0 ? void 0 : options.progId;
127
+ }
128
+ put(partName, bytes, options) {
129
+ var _a;
130
+ const name = partName.startsWith('/') ? partName : '/' + partName;
131
+ if (name === '/' + CONTENT_TYPES_NAME)
132
+ return; // content types travel on each part
133
+ const contentType = (_a = options === null || options === void 0 ? void 0 : options.contentType) !== null && _a !== void 0 ? _a : (name.endsWith('.rels') ? ContentTypes.RELATIONSHIPS_PART : ContentTypes.OCTET_STREAM);
134
+ const xml = isXmlContentType(contentType) || name.endsWith('.xml') || name.endsWith('.rels');
135
+ if (xml) {
136
+ const text = stripXmlDeclaration(decodeXmlText(bytes));
137
+ this.parts.push(`<pkg:part pkg:name="${escapeAttr(name)}" pkg:contentType="${escapeAttr(contentType)}"><pkg:xmlData>${text}</pkg:xmlData></pkg:part>`);
138
+ }
139
+ else {
140
+ const b64 = base64Encode(bytes).replace(/.{76}/g, '$&\r\n');
141
+ this.parts.push(`<pkg:part pkg:name="${escapeAttr(name)}" pkg:contentType="${escapeAttr(contentType)}" pkg:compression="store"><pkg:binaryData>${b64}</pkg:binaryData></pkg:part>`);
142
+ }
143
+ }
144
+ async finish() {
145
+ const pi = this.progId ? `<?mso-application progid="${escapeAttr(this.progId)}"?>` : '';
146
+ return `<?xml version="1.0" standalone="yes"?>${pi}<pkg:package xmlns:pkg="${PKG_NS}">${this.parts.join('')}</pkg:package>`;
147
+ }
148
+ }
149
+ function escapeAttr(s) {
150
+ return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/"/g, '&quot;');
151
+ }
@@ -0,0 +1,18 @@
1
+ import type { PartStore } from './PartStore.mjs';
2
+ import { type DomPreprocessor } from '../parts/XmlPart.mjs';
3
+ import { PartRegistry } from '../parts/PartRegistry.mjs';
4
+ import type { OpcPackage } from '../packages/OpcPackage.mjs';
5
+ export interface LoadOptions {
6
+ /** Part classes by content type; defaults to docx4j's table. */
7
+ registry?: PartRegistry;
8
+ /** Resolve `mc:AlternateContent` before unmarshalling (default true, as docx4j and Word). */
9
+ mcePreprocess?: boolean;
10
+ /** Runs over every XML part's DOM before it is unmarshalled; set by the package from `mcePreprocess`. */
11
+ preprocessor?: DomPreprocessor;
12
+ }
13
+ /**
14
+ * Loads a package from a container (docx4j io3.Load3): content types, then the package
15
+ * relationships, then every part reachable through relationships, recursively. Parts are not
16
+ * unmarshalled. `createPackage` picks the package class from the main part's content type.
17
+ */
18
+ export declare function loadPackage<P extends OpcPackage>(store: PartStore, createPackage: (mainPartContentType: string | undefined) => P, options?: LoadOptions): Promise<P>;
@@ -0,0 +1,123 @@
1
+ import { PartName, CONTENT_TYPES_NAME } from './PartName.mjs';
2
+ import { ContentTypeManager } from './ContentTypeManager.mjs';
3
+ import { Docx4JException, InvalidFormatException } from './exceptions.mjs';
4
+ import { Namespaces } from '../parts/Namespaces.mjs';
5
+ import { RelationshipsPart, isExternal } from '../parts/RelationshipsPart.mjs';
6
+ import { XmlPart } from '../parts/XmlPart.mjs';
7
+ import { CustomXmlDataStoragePart } from '../parts/DefaultXmlPart.mjs';
8
+ import { CustomXmlDataStoragePropertiesPart, DS_NS } from '../parts/customXml/index.mjs';
9
+ import { defaultPartRegistry } from '../parts/PartRegistry.mjs';
10
+ import { decodeXmlText, parseXml } from '../xml/dom.mjs';
11
+ /** Relationship types whose target is the main part (docx4j PackageRelsUtil.getNameOfMainPart). */
12
+ const MAIN_PART_RELATIONSHIPS = [
13
+ Namespaces.DOCUMENT,
14
+ 'http://schemas.microsoft.com/office/2006/relationships/graphicFrameDoc',
15
+ Namespaces.DRAWINGML_DIAGRAM_LAYOUT,
16
+ Namespaces.DOCUMENT_STRICT,
17
+ ];
18
+ /**
19
+ * Loads a package from a container (docx4j io3.Load3): content types, then the package
20
+ * relationships, then every part reachable through relationships, recursively. Parts are not
21
+ * unmarshalled. `createPackage` picks the package class from the main part's content type.
22
+ */
23
+ export async function loadPackage(store, createPackage, options = {}) {
24
+ var _a;
25
+ const registry = (_a = options.registry) !== null && _a !== void 0 ? _a : defaultPartRegistry;
26
+ if (!store.has(CONTENT_TYPES_NAME))
27
+ throw new InvalidFormatException(`${CONTENT_TYPES_NAME} is missing from this package`);
28
+ const ctm = ContentTypeManager.parse(decodeXmlText(await store.load(CONTENT_TYPES_NAME)));
29
+ const packageRels = RelationshipsPart.createPackageRels();
30
+ if (!store.has('_rels/.rels'))
31
+ throw new InvalidFormatException('_rels/.rels appears to be missing from this package');
32
+ packageRels.setBytes(await store.load('_rels/.rels'));
33
+ await packageRels.getContents();
34
+ const mainRel = packageRels.list.find((r) => MAIN_PART_RELATIONSHIPS.includes(r.type));
35
+ if (!mainRel)
36
+ throw new InvalidFormatException('No relationship of type officeDocument');
37
+ const mainPartName = PartName.resolve(PartName.ROOT, mainRel.target);
38
+ const pkg = createPackage(ctm.getContentType(mainPartName));
39
+ pkg.contentTypeManager = ctm;
40
+ pkg.sourcePartStore = store;
41
+ pkg.relationshipsPart = packageRels;
42
+ packageRels.sourceP = pkg;
43
+ packageRels.package = pkg;
44
+ const loader = new Loader(pkg, store, registry, options.preprocessor);
45
+ await loader.addPartsFromRelationships(pkg, packageRels);
46
+ await loader.registerCustomXmlDataStorageParts();
47
+ return pkg;
48
+ }
49
+ class Loader {
50
+ constructor(pkg, store, registry, preprocessor) {
51
+ this.pkg = pkg;
52
+ this.store = store;
53
+ this.registry = registry;
54
+ this.preprocessor = preprocessor;
55
+ this.handled = new Set();
56
+ }
57
+ async addPartsFromRelationships(source, rp) {
58
+ for (const r of rp.list) {
59
+ try {
60
+ await this.getPart(source, rp, r);
61
+ }
62
+ catch (e) {
63
+ if (e instanceof Docx4JException)
64
+ throw e;
65
+ throw new Docx4JException(`Failed to add parts from relationships of ${rp.partName}`, { cause: e });
66
+ }
67
+ }
68
+ }
69
+ async getPart(source, rp, r) {
70
+ if (r.type === Namespaces.HYPERLINK || isExternal(r))
71
+ return;
72
+ const partName = rp.resolveTarget(r);
73
+ const existing = this.handled.has(partName.key) ? this.pkg.getPart(partName) : undefined;
74
+ if (existing) {
75
+ // A part can be the target of several relationships (a header used by two sections, an image twice).
76
+ source.setPartShortcut(existing, r.type);
77
+ existing.sourceRelationships.push(r);
78
+ return;
79
+ }
80
+ const part = this.createPart(partName, r, rp);
81
+ source.setPartShortcut(part, r.type);
82
+ if (part.relationshipType === '')
83
+ part.relationshipType = r.type;
84
+ if (part instanceof XmlPart && this.preprocessor)
85
+ part.preprocessor = this.preprocessor;
86
+ rp.loadPart(part, r);
87
+ this.handled.add(partName.key);
88
+ const relsName = PartName.relsFor(partName).storeName;
89
+ if (this.store.has(relsName)) {
90
+ const rrp = new RelationshipsPart(part);
91
+ rrp.setBytes(await this.store.load(relsName));
92
+ await rrp.getContents();
93
+ part.relationshipsPart = rrp;
94
+ await this.addPartsFromRelationships(part, rrp);
95
+ }
96
+ }
97
+ createPart(partName, r, rp) {
98
+ const storeName = partName.storeName;
99
+ if (!this.store.has(storeName) && !this.store.has(decodeURIComponent(storeName))) {
100
+ throw new Docx4JException(`For source ${rp.sourceName}, cannot find part ${partName} from rel ${r.id}=${r.target}`);
101
+ }
102
+ return this.registry.createPart(partName, this.pkg.contentTypeManager.getContentType(partName), r);
103
+ }
104
+ /** Indexes custom XML parts by the itemID of their properties part (docx4j Load.registerCustomXmlDataStorageParts). */
105
+ async registerCustomXmlDataStorageParts() {
106
+ var _a, _b;
107
+ for (const part of this.pkg.parts) {
108
+ if (!(part instanceof CustomXmlDataStoragePart))
109
+ continue;
110
+ const rel = (_a = part.relationshipsPart) === null || _a === void 0 ? void 0 : _a.getRelationshipByType(Namespaces.CUSTOM_XML_DATA_STORAGE_PROPERTIES);
111
+ const props = rel ? (_b = part.relationshipsPart) === null || _b === void 0 ? void 0 : _b.getPart(rel) : undefined;
112
+ if (!(props instanceof CustomXmlDataStoragePropertiesPart))
113
+ continue;
114
+ // Read the itemID from the DOM rather than unmarshalling, so the properties part stays untouched.
115
+ const doc = parseXml(decodeXmlText(await props.getBytes()));
116
+ const itemId = doc.documentElement.getAttributeNS(DS_NS, 'itemID') || doc.documentElement.getAttribute('ds:itemID');
117
+ if (!itemId)
118
+ continue;
119
+ part.itemId = itemId.toLowerCase();
120
+ this.pkg.customXmlDataStorageParts.set(part.itemId, part);
121
+ }
122
+ }
123
+ }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * An OPC part name (docx4j org.docx4j.openpackaging.parts.PartName): a leading '/', no trailing
3
+ * '/', no empty segments, no segment ending in '.', percent-encoding allowed. Equality is
4
+ * case-insensitive, as Word writes mixed case (`[Content_Types].xml`, `_rels/.rels`).
5
+ */
6
+ export declare class PartName {
7
+ /** The name as given, e.g. '/word/document.xml'. */
8
+ readonly name: string;
9
+ /** Lower-cased name, the key for maps. */
10
+ readonly key: string;
11
+ private constructor();
12
+ /** The package root, the source of the package relationships. Not a valid part name otherwise. */
13
+ static readonly ROOT: PartName;
14
+ /** Validates and wraps a part name; a leading '/' is added when missing. */
15
+ static of(name: string | PartName): PartName;
16
+ /** 'xml' for '/word/document.xml'; '' when there is no dot. */
17
+ get extension(): string;
18
+ /** '/word' for '/word/document.xml'; '/' for '/[Content_Types].xml'. */
19
+ get directory(): string;
20
+ /** 'document.xml' for '/word/document.xml'. */
21
+ get fileName(): string;
22
+ /** The name as stored in a container: without the leading '/'. */
23
+ get storeName(): string;
24
+ /** True for '/_rels/.rels' and '/word/_rels/document.xml.rels'. */
25
+ get isRelationshipsPart(): boolean;
26
+ equals(other: PartName | string | undefined): boolean;
27
+ toString(): string;
28
+ /** The relationships part name for a part: '/word/_rels/document.xml.rels'; for the root, '/_rels/.rels'. */
29
+ static relsFor(source: PartName | string): PartName;
30
+ /** The inverse of relsFor: '/word/document.xml' for '/word/_rels/document.xml.rels'; ROOT for '/_rels/.rels'. */
31
+ static sourceOfRels(rels: PartName | string): PartName;
32
+ /**
33
+ * Resolves a relationship target against its source part (docx4j URIHelper.resolvePartUri):
34
+ * relative to the source's directory, '.' and '..' collapsed; a target with a leading '/' is
35
+ * absolute. The source may be the package root.
36
+ */
37
+ static resolve(source: PartName | string, target: string): PartName;
38
+ /**
39
+ * The relationship target for a part from a source part (docx4j URIHelper.relativizeURI): from
40
+ * '/word/document.xml' to '/word/media/image1.png' is 'media/image1.png'; from the root it is
41
+ * the name without the leading '/'.
42
+ */
43
+ static relativize(source: PartName | string, target: PartName | string): string;
44
+ }
45
+ /** The container name of the content types stream; not a part, never in `parts`. */
46
+ export declare const CONTENT_TYPES_NAME = "[Content_Types].xml";
@@ -0,0 +1,132 @@
1
+ import { InvalidFormatException } from './exceptions.mjs';
2
+ /**
3
+ * An OPC part name (docx4j org.docx4j.openpackaging.parts.PartName): a leading '/', no trailing
4
+ * '/', no empty segments, no segment ending in '.', percent-encoding allowed. Equality is
5
+ * case-insensitive, as Word writes mixed case (`[Content_Types].xml`, `_rels/.rels`).
6
+ */
7
+ export class PartName {
8
+ constructor(name) {
9
+ this.name = name;
10
+ this.key = name.toLowerCase();
11
+ }
12
+ /** Validates and wraps a part name; a leading '/' is added when missing. */
13
+ static of(name) {
14
+ if (name instanceof PartName)
15
+ return name;
16
+ if (typeof name !== 'string' || name.length === 0)
17
+ throw new InvalidFormatException('A part name shall not be empty [M1.1]');
18
+ const full = name.startsWith('/') ? name : '/' + name;
19
+ if (full === '/')
20
+ throw new InvalidFormatException('A part name shall not be empty [M1.1]');
21
+ if (full.endsWith('/'))
22
+ throw new InvalidFormatException(`A part name shall not have a forward slash as the last character [M1.5]: ${full}`);
23
+ if (/^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(name))
24
+ throw new InvalidFormatException(`Absolute URI forbidden: ${name}`);
25
+ for (const seg of full.substring(1).split('/')) {
26
+ if (seg.length === 0)
27
+ throw new InvalidFormatException(`A part name shall not have empty segments [M1.3]: ${full}`);
28
+ if (seg.endsWith('.'))
29
+ throw new InvalidFormatException(`A segment shall not end with a dot ('.') character [M1.9]: ${full}`);
30
+ if (/^\.+$/.test(seg))
31
+ throw new InvalidFormatException(`A segment shall include at least one non-dot character [M1.10]: ${full}`);
32
+ if (/[\\#?\s]/.test(seg))
33
+ throw new InvalidFormatException(`A segment shall not hold any characters other than pchar characters [M1.6]: ${full}`);
34
+ if (/%(?![0-9A-Fa-f]{2})/.test(seg))
35
+ throw new InvalidFormatException(`The segment ${seg} contains an invalid encoded character`);
36
+ if (/%2[fF]|%5[cC]/.test(seg))
37
+ throw new InvalidFormatException(`A segment shall not contain a percent-encoded forward or backward slash [M1.7]: ${full}`);
38
+ }
39
+ return new PartName(full);
40
+ }
41
+ /** 'xml' for '/word/document.xml'; '' when there is no dot. */
42
+ get extension() {
43
+ const last = this.name.substring(this.name.lastIndexOf('/') + 1);
44
+ const i = last.lastIndexOf('.');
45
+ return i > -1 ? last.substring(i + 1) : '';
46
+ }
47
+ /** '/word' for '/word/document.xml'; '/' for '/[Content_Types].xml'. */
48
+ get directory() {
49
+ const i = this.name.lastIndexOf('/');
50
+ return i === 0 ? '/' : this.name.substring(0, i);
51
+ }
52
+ /** 'document.xml' for '/word/document.xml'. */
53
+ get fileName() {
54
+ return this.name.substring(this.name.lastIndexOf('/') + 1);
55
+ }
56
+ /** The name as stored in a container: without the leading '/'. */
57
+ get storeName() {
58
+ return this.name.substring(1);
59
+ }
60
+ /** True for '/_rels/.rels' and '/word/_rels/document.xml.rels'. */
61
+ get isRelationshipsPart() {
62
+ return /\/_rels\/[^/]*\.rels$/.test(this.key);
63
+ }
64
+ equals(other) {
65
+ if (other === undefined)
66
+ return false;
67
+ return this.key === (typeof other === 'string' ? other.toLowerCase() : other.key);
68
+ }
69
+ toString() {
70
+ return this.name;
71
+ }
72
+ /** The relationships part name for a part: '/word/_rels/document.xml.rels'; for the root, '/_rels/.rels'. */
73
+ static relsFor(source) {
74
+ const s = typeof source === 'string' ? source : source.name;
75
+ if (s === '/')
76
+ return PACKAGE_RELS;
77
+ const pos = s.lastIndexOf('/');
78
+ return new PartName(s.substring(0, pos) + '/_rels' + s.substring(pos) + '.rels');
79
+ }
80
+ /** The inverse of relsFor: '/word/document.xml' for '/word/_rels/document.xml.rels'; ROOT for '/_rels/.rels'. */
81
+ static sourceOfRels(rels) {
82
+ const s = typeof rels === 'string' ? rels : rels.name;
83
+ const m = /^(.*)\/_rels\/([^/]*)\.rels$/.exec(s);
84
+ if (!m)
85
+ throw new InvalidFormatException(`Not a relationships part name: ${s}`);
86
+ if (m[1] === '' && m[2] === '')
87
+ return PartName.ROOT;
88
+ return new PartName(`${m[1]}/${m[2]}`);
89
+ }
90
+ /**
91
+ * Resolves a relationship target against its source part (docx4j URIHelper.resolvePartUri):
92
+ * relative to the source's directory, '.' and '..' collapsed; a target with a leading '/' is
93
+ * absolute. The source may be the package root.
94
+ */
95
+ static resolve(source, target) {
96
+ const s = typeof source === 'string' ? source : source.name;
97
+ const base = s === '/' ? '/' : s.substring(0, s.lastIndexOf('/') + 1);
98
+ const path = target.startsWith('/') ? target : base + target;
99
+ const out = [];
100
+ for (const seg of path.split('/')) {
101
+ if (seg === '' || seg === '.')
102
+ continue;
103
+ if (seg === '..')
104
+ out.pop();
105
+ else
106
+ out.push(seg);
107
+ }
108
+ return PartName.of('/' + out.join('/'));
109
+ }
110
+ /**
111
+ * The relationship target for a part from a source part (docx4j URIHelper.relativizeURI): from
112
+ * '/word/document.xml' to '/word/media/image1.png' is 'media/image1.png'; from the root it is
113
+ * the name without the leading '/'.
114
+ */
115
+ static relativize(source, target) {
116
+ const s = typeof source === 'string' ? source : source.name;
117
+ const t = typeof target === 'string' ? target : target.name;
118
+ if (s === '/')
119
+ return t.substring(1);
120
+ const from = s.substring(1, s.lastIndexOf('/') + 1).split('/').filter((x) => x !== '');
121
+ const to = t.substring(1).split('/');
122
+ let i = 0;
123
+ while (i < from.length && i < to.length - 1 && from[i] === to[i])
124
+ i++;
125
+ return '../'.repeat(from.length - i) + to.slice(i).join('/');
126
+ }
127
+ }
128
+ /** The package root, the source of the package relationships. Not a valid part name otherwise. */
129
+ PartName.ROOT = new PartName('/');
130
+ const PACKAGE_RELS = PartName.of('/_rels/.rels');
131
+ /** The container name of the content types stream; not a part, never in `parts`. */
132
+ export const CONTENT_TYPES_NAME = '[Content_Types].xml';
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Where a package's bytes come from (docx4j io3.stores.PartStore, the load half). Names are as
3
+ * stored, without the leading '/' (`word/document.xml`, `[Content_Types].xml`, `_rels/.rels`).
4
+ */
5
+ export interface PartStore {
6
+ /** Names in container order. */
7
+ partNames(): Iterable<string>;
8
+ has(partName: string): boolean;
9
+ /** Raw bytes of a part. Implementations may inflate lazily on first access. */
10
+ load(partName: string): Promise<Uint8Array>;
11
+ /** Size if known without loading, else undefined. */
12
+ size?(partName: string): number | undefined;
13
+ }
14
+ export interface PutOptions {
15
+ /** Deflate in a zip (default true); ignored by containers that do not compress. */
16
+ compress?: boolean;
17
+ /** The part's content type, needed by containers that carry it per part (flat OPC). */
18
+ contentType?: string;
19
+ }
20
+ /** Where a package's bytes go (docx4j PartStore, the save half). `R` is what `finish` produces. */
21
+ export interface PartSink<R = unknown> {
22
+ put(partName: string, bytes: Uint8Array, options?: PutOptions): void;
23
+ /** Completes the container. */
24
+ finish(): Promise<R>;
25
+ }
26
+ /** An in-memory container: new packages, tests, and the result of `MemoryPartSink`. */
27
+ export declare class MemoryPartStore implements PartStore, PartSink<MemoryPartStore> {
28
+ private readonly parts;
29
+ /** Content types as put, for stores filled through the sink side. */
30
+ readonly contentTypes: Map<string, string>;
31
+ constructor(entries?: Iterable<[string, Uint8Array]>);
32
+ partNames(): Iterable<string>;
33
+ has(partName: string): boolean;
34
+ load(partName: string): Promise<Uint8Array>;
35
+ size(partName: string): number | undefined;
36
+ put(partName: string, bytes: Uint8Array, options?: PutOptions): void;
37
+ delete(partName: string): boolean;
38
+ finish(): Promise<MemoryPartStore>;
39
+ }
40
+ /** A sink that collects into a fresh `MemoryPartStore`. */
41
+ export declare class MemoryPartSink implements PartSink<MemoryPartStore> {
42
+ readonly store: MemoryPartStore;
43
+ put(partName: string, bytes: Uint8Array, options?: PutOptions): void;
44
+ finish(): Promise<MemoryPartStore>;
45
+ }
@@ -0,0 +1,54 @@
1
+ /** An in-memory container: new packages, tests, and the result of `MemoryPartSink`. */
2
+ export class MemoryPartStore {
3
+ constructor(entries) {
4
+ this.parts = new Map();
5
+ /** Content types as put, for stores filled through the sink side. */
6
+ this.contentTypes = new Map();
7
+ if (entries)
8
+ for (const [name, bytes] of entries)
9
+ this.parts.set(normalize(name), bytes);
10
+ }
11
+ partNames() {
12
+ return this.parts.keys();
13
+ }
14
+ has(partName) {
15
+ return this.parts.has(normalize(partName));
16
+ }
17
+ async load(partName) {
18
+ const bytes = this.parts.get(normalize(partName));
19
+ if (bytes === undefined)
20
+ throw new Error(`No part ${partName}`);
21
+ return bytes;
22
+ }
23
+ size(partName) {
24
+ var _a;
25
+ return (_a = this.parts.get(normalize(partName))) === null || _a === void 0 ? void 0 : _a.length;
26
+ }
27
+ put(partName, bytes, options) {
28
+ const name = normalize(partName);
29
+ this.parts.set(name, bytes);
30
+ if ((options === null || options === void 0 ? void 0 : options.contentType) !== undefined)
31
+ this.contentTypes.set(name, options.contentType);
32
+ }
33
+ delete(partName) {
34
+ return this.parts.delete(normalize(partName));
35
+ }
36
+ async finish() {
37
+ return this;
38
+ }
39
+ }
40
+ /** A sink that collects into a fresh `MemoryPartStore`. */
41
+ export class MemoryPartSink {
42
+ constructor() {
43
+ this.store = new MemoryPartStore();
44
+ }
45
+ put(partName, bytes, options) {
46
+ this.store.put(partName, bytes, options);
47
+ }
48
+ async finish() {
49
+ return this.store;
50
+ }
51
+ }
52
+ function normalize(name) {
53
+ return name.startsWith('/') ? name.substring(1) : name;
54
+ }
@@ -0,0 +1,9 @@
1
+ import type { PartSink } from './PartStore.mjs';
2
+ import type { OpcPackage } from '../packages/OpcPackage.mjs';
3
+ /**
4
+ * Writes a package to a sink (docx4j io3.Save): `[Content_Types].xml` first, then the package
5
+ * relationships, then every part reachable through relationships, each once. Relationship parts
6
+ * and unmarshalled XML parts are marshalled; every other part's bytes come from its source
7
+ * container unchanged.
8
+ */
9
+ export declare function savePackage<R>(pkg: OpcPackage, sink: PartSink<R>): Promise<R>;
@@ -0,0 +1,52 @@
1
+ import { CONTENT_TYPES_NAME } from './PartName.mjs';
2
+ import { ContentTypes } from './ContentTypes.mjs';
3
+ import { Docx4JException } from './exceptions.mjs';
4
+ import { Namespaces } from '../parts/Namespaces.mjs';
5
+ import { isExternal } from '../parts/RelationshipsPart.mjs';
6
+ import { encodeText } from '../xml/dom.mjs';
7
+ /**
8
+ * Writes a package to a sink (docx4j io3.Save): `[Content_Types].xml` first, then the package
9
+ * relationships, then every part reachable through relationships, each once. Relationship parts
10
+ * and unmarshalled XML parts are marshalled; every other part's bytes come from its source
11
+ * container unchanged.
12
+ */
13
+ export async function savePackage(pkg, sink) {
14
+ const saver = new Saver(pkg, sink);
15
+ sink.put(CONTENT_TYPES_NAME, encodeText(pkg.contentTypeManager.toXml()), { contentType: ContentTypes.CONTENT_TYPES_PART });
16
+ await saver.saveRels(pkg.relationshipsPart);
17
+ return sink.finish();
18
+ }
19
+ class Saver {
20
+ constructor(pkg, sink) {
21
+ this.pkg = pkg;
22
+ this.sink = sink;
23
+ this.handled = new Set();
24
+ }
25
+ async saveRels(rp) {
26
+ this.sink.put(rp.partName.storeName, await rp.getBytes(), { contentType: ContentTypes.RELATIONSHIPS_PART });
27
+ for (const r of rp.list) {
28
+ if (r.type === Namespaces.HYPERLINK || isExternal(r))
29
+ continue;
30
+ const part = this.pkg.getPart(rp.resolveTarget(r));
31
+ if (!part)
32
+ throw new Docx4JException(`Part ${rp.resolveTarget(r)} (target of ${r.id} in ${rp.partName}) not found`);
33
+ await this.savePart(part);
34
+ }
35
+ }
36
+ async savePart(part) {
37
+ if (this.handled.has(part.partName.key))
38
+ return;
39
+ this.handled.add(part.partName.key);
40
+ let bytes;
41
+ try {
42
+ bytes = await part.getBytes();
43
+ }
44
+ catch (e) {
45
+ throw new Docx4JException(`Problem saving part ${part.partName}`, { cause: e });
46
+ }
47
+ this.sink.put(part.partName.storeName, bytes, { compress: part.compress, contentType: part.contentType });
48
+ const rrp = part.relationshipsPart;
49
+ if (rrp && rrp.size > 0)
50
+ await this.saveRels(rrp);
51
+ }
52
+ }