@docx4j/core-ts 0.5.0 → 0.7.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 (207) hide show
  1. package/NOTICE +4 -0
  2. package/README.md +92 -4
  3. package/dist/html/convert.mjs +2 -1
  4. package/dist/html/elements.mjs +2 -1
  5. package/dist/model/color/ColorMath.d.mts +29 -0
  6. package/dist/model/color/ColorMath.mjs +146 -0
  7. package/dist/model/color/ColorTables.d.mts +6 -0
  8. package/dist/model/color/ColorTables.mjs +83 -0
  9. package/dist/model/color/DrawingMLColors.d.mts +52 -0
  10. package/dist/model/color/DrawingMLColors.mjs +374 -0
  11. package/dist/model/color/ResolvedColor.d.mts +36 -0
  12. package/dist/model/color/ResolvedColor.mjs +65 -0
  13. package/dist/model/color/SpreadsheetColors.d.mts +36 -0
  14. package/dist/model/color/SpreadsheetColors.mjs +84 -0
  15. package/dist/model/color/StyleFill.d.mts +28 -0
  16. package/dist/model/color/StyleFill.mjs +21 -0
  17. package/dist/model/color/ThemeColors.d.mts +86 -0
  18. package/dist/model/color/ThemeColors.mjs +304 -0
  19. package/dist/model/color/WordColors.d.mts +71 -0
  20. package/dist/model/color/WordColors.mjs +121 -0
  21. package/dist/model/color/index.d.mts +6 -0
  22. package/dist/model/color/index.mjs +30 -0
  23. package/dist/model/content/Body.d.mts +145 -23
  24. package/dist/model/content/Body.mjs +507 -150
  25. package/dist/model/content/Comment.d.mts +23 -5
  26. package/dist/model/content/Comment.mjs +207 -26
  27. package/dist/model/content/ContentControl.d.mts +198 -19
  28. package/dist/model/content/ContentControl.mjs +672 -99
  29. package/dist/model/content/Font.d.mts +70 -17
  30. package/dist/model/content/Font.mjs +119 -36
  31. package/dist/model/content/InlinePicture.d.mts +11 -0
  32. package/dist/model/content/InlinePicture.mjs +21 -1
  33. package/dist/model/content/List.d.mts +6 -3
  34. package/dist/model/content/List.mjs +18 -8
  35. package/dist/model/content/Paragraph.d.mts +200 -14
  36. package/dist/model/content/Paragraph.mjs +898 -82
  37. package/dist/model/content/Range.d.mts +227 -12
  38. package/dist/model/content/Range.mjs +785 -59
  39. package/dist/model/content/ResolvedView.d.mts +104 -0
  40. package/dist/model/content/ResolvedView.mjs +596 -0
  41. package/dist/model/content/Table.d.mts +29 -3
  42. package/dist/model/content/Table.mjs +135 -49
  43. package/dist/model/content/TrackedChange.d.mts +174 -15
  44. package/dist/model/content/TrackedChange.mjs +734 -170
  45. package/dist/model/content/comments.d.mts +20 -6
  46. package/dist/model/content/comments.mjs +67 -57
  47. package/dist/model/content/generalException.d.mts +18 -0
  48. package/dist/model/content/generalException.mjs +24 -0
  49. package/dist/model/content/index.d.mts +10 -3
  50. package/dist/model/content/index.mjs +10 -2
  51. package/dist/model/content/mathText.d.mts +21 -0
  52. package/dist/model/content/mathText.mjs +267 -0
  53. package/dist/model/content/officeText.d.mts +27 -0
  54. package/dist/model/content/officeText.mjs +74 -0
  55. package/dist/model/content/ooxml.mjs +12 -1
  56. package/dist/model/content/outline.d.mts +5 -0
  57. package/dist/model/content/outline.mjs +13 -0
  58. package/dist/model/content/paraIds.d.mts +5 -0
  59. package/dist/model/content/paraIds.mjs +54 -0
  60. package/dist/model/content/placeholders.d.mts +110 -0
  61. package/dist/model/content/placeholders.mjs +253 -0
  62. package/dist/model/content/search.d.mts +74 -0
  63. package/dist/model/content/search.mjs +255 -9
  64. package/dist/model/content/spanEdits.d.mts +159 -0
  65. package/dist/model/content/spanEdits.mjs +728 -0
  66. package/dist/model/content/story.d.mts +47 -0
  67. package/dist/model/content/story.mjs +95 -0
  68. package/dist/model/content/tableColumns.d.mts +1 -1
  69. package/dist/model/content/tableColumns.mjs +108 -80
  70. package/dist/model/content/tableRevisions.d.mts +11 -3
  71. package/dist/model/content/tableRevisions.mjs +72 -42
  72. package/dist/model/content/tableRules.d.mts +30 -0
  73. package/dist/model/content/tableRules.mjs +102 -0
  74. package/dist/model/content/textBoxes.d.mts +18 -0
  75. package/dist/model/content/textBoxes.mjs +105 -0
  76. package/dist/model/content/trackedChangeFilter.d.mts +39 -0
  77. package/dist/model/content/trackedChangeFilter.mjs +81 -0
  78. package/dist/model/content/tracking.d.mts +63 -6
  79. package/dist/model/content/tracking.mjs +242 -22
  80. package/dist/model/content/tree.d.mts +172 -24
  81. package/dist/model/content/tree.mjs +328 -54
  82. package/dist/model/content/wordRange.d.mts +48 -0
  83. package/dist/model/content/wordRange.mjs +97 -0
  84. package/dist/model/customxml/CustomXmlPartCollection.d.mts +30 -1
  85. package/dist/model/customxml/CustomXmlPartCollection.mjs +81 -12
  86. package/dist/model/customxml/XmlMapping.d.mts +32 -8
  87. package/dist/model/customxml/XmlMapping.mjs +64 -10
  88. package/dist/model/customxml/bindings.d.mts +33 -4
  89. package/dist/model/customxml/bindings.mjs +171 -45
  90. package/dist/model/customxml/index.d.mts +1 -0
  91. package/dist/model/customxml/index.mjs +1 -0
  92. package/dist/model/customxml/kinds.d.mts +8 -3
  93. package/dist/model/customxml/kinds.mjs +26 -5
  94. package/dist/model/customxml/opendope.d.mts +4 -1
  95. package/dist/model/customxml/opendope.mjs +9 -6
  96. package/dist/model/customxml/picturePlaceholder.d.mts +17 -0
  97. package/dist/model/customxml/picturePlaceholder.mjs +32 -0
  98. package/dist/model/customxml/pictures.d.mts +4 -3
  99. package/dist/model/customxml/pictures.mjs +22 -8
  100. package/dist/model/customxml/storeItems.d.mts +57 -0
  101. package/dist/model/customxml/storeItems.mjs +122 -0
  102. package/dist/model/customxml/xhtml.mjs +23 -22
  103. package/dist/model/dateZone.d.mts +25 -0
  104. package/dist/model/dateZone.mjs +57 -0
  105. package/dist/model/index.d.mts +3 -0
  106. package/dist/model/index.mjs +5 -0
  107. package/dist/model/listnumbering/Emulator.d.mts +37 -5
  108. package/dist/model/listnumbering/Emulator.mjs +63 -9
  109. package/dist/model/properties/PropertyResolver.d.mts +74 -2
  110. package/dist/model/properties/PropertyResolver.mjs +133 -5
  111. package/dist/model/properties/catalogue.d.mts +19 -5
  112. package/dist/model/properties/catalogue.mjs +41 -15
  113. package/dist/model/properties/index.d.mts +1 -1
  114. package/dist/model/properties/log.d.mts +1 -1
  115. package/dist/model/properties/log.mjs +5 -5
  116. package/dist/model/properties/styleUtil.d.mts +3 -1
  117. package/dist/model/properties/styleUtil.mjs +20 -24
  118. package/dist/model/properties/table.mjs +4 -2
  119. package/dist/model/text/CaseMapping.d.mts +38 -0
  120. package/dist/model/text/CaseMapping.mjs +86 -0
  121. package/dist/model/text/Extractor.d.mts +349 -0
  122. package/dist/model/text/Extractor.mjs +2406 -0
  123. package/dist/model/text/LinkTargets.d.mts +45 -0
  124. package/dist/model/text/LinkTargets.mjs +170 -0
  125. package/dist/model/text/MarkdownExporter.d.mts +140 -0
  126. package/dist/model/text/MarkdownExporter.mjs +255 -0
  127. package/dist/model/text/PlainText.d.mts +29 -0
  128. package/dist/model/text/PlainText.mjs +542 -0
  129. package/dist/model/text/TextContext.d.mts +256 -0
  130. package/dist/model/text/TextContext.mjs +600 -0
  131. package/dist/model/text/TextOptions.d.mts +233 -0
  132. package/dist/model/text/TextOptions.mjs +419 -0
  133. package/dist/model/text/TextResult.d.mts +621 -0
  134. package/dist/model/text/TextResult.mjs +629 -0
  135. package/dist/model/text/Texts.d.mts +99 -0
  136. package/dist/model/text/Texts.mjs +187 -0
  137. package/dist/model/text/WmlToMarkdown.d.mts +120 -0
  138. package/dist/model/text/WmlToMarkdown.mjs +936 -0
  139. package/dist/model/text/caseMapping.generated.d.mts +7 -0
  140. package/dist/model/text/caseMapping.generated.mjs +214 -0
  141. package/dist/model/text/commonmark.d.mts +121 -0
  142. package/dist/model/text/commonmark.mjs +655 -0
  143. package/dist/model/text/index.d.mts +8 -0
  144. package/dist/model/text/index.mjs +9 -0
  145. package/dist/model/text/normalize.d.mts +8 -0
  146. package/dist/model/text/normalize.mjs +13 -0
  147. package/dist/model/text/outline.d.mts +4 -0
  148. package/dist/model/text/outline.mjs +71 -0
  149. package/dist/model/wordIntegers.d.mts +29 -0
  150. package/dist/model/wordIntegers.mjs +78 -0
  151. package/dist/office-js/document.mjs +15 -1
  152. package/dist/office-js/fragment.d.mts +7 -5
  153. package/dist/office-js/fragment.mjs +63 -14
  154. package/dist/office-js/proxy.d.mts +8 -0
  155. package/dist/office-js/proxy.mjs +29 -0
  156. package/dist/office-js/run.mjs +2 -0
  157. package/dist/office-js/supported.generated.mjs +16 -0
  158. package/dist/office-js/toApiScript.mjs +29 -12
  159. package/dist/opc/DirectoryPartStore.d.mts +6 -2
  160. package/dist/opc/DirectoryPartStore.mjs +11 -4
  161. package/dist/opc/FlatOpcPartStore.d.mts +4 -2
  162. package/dist/opc/FlatOpcPartStore.mjs +11 -8
  163. package/dist/opc/Load.d.mts +32 -0
  164. package/dist/opc/Load.mjs +115 -5
  165. package/dist/opc/PartStore.d.mts +34 -1
  166. package/dist/opc/PartStore.mjs +71 -8
  167. package/dist/opc/Save.d.mts +32 -0
  168. package/dist/opc/Save.mjs +110 -4
  169. package/dist/opc/ZipPartStore.d.mts +4 -2
  170. package/dist/opc/ZipPartStore.mjs +12 -7
  171. package/dist/opc/exceptions.d.mts +140 -0
  172. package/dist/opc/exceptions.mjs +108 -0
  173. package/dist/opc/index.d.mts +8 -5
  174. package/dist/opc/index.mjs +5 -3
  175. package/dist/opc/mce/preprocessor.d.mts +141 -5
  176. package/dist/opc/mce/preprocessor.mjs +119 -18
  177. package/dist/opc/mce/understood.d.mts +3 -11
  178. package/dist/opc/mce/understood.mjs +27 -23
  179. package/dist/opc/repair/breaks.d.mts +23 -0
  180. package/dist/opc/repair/breaks.mjs +288 -0
  181. package/dist/opc/repair/hosts.d.mts +12 -0
  182. package/dist/opc/repair/hosts.mjs +33 -0
  183. package/dist/opc/repair/mainRoot.d.mts +19 -0
  184. package/dist/opc/repair/mainRoot.mjs +168 -0
  185. package/dist/opc/repair/unknownElements.d.mts +8 -0
  186. package/dist/opc/repair/unknownElements.mjs +180 -0
  187. package/dist/opc/repair/wmlNames.generated.d.mts +2 -0
  188. package/dist/opc/repair/wmlNames.generated.mjs +80 -0
  189. package/dist/opc/skipped.d.mts +50 -0
  190. package/dist/opc/skipped.mjs +140 -0
  191. package/dist/packages/OpcPackage.d.mts +110 -4
  192. package/dist/packages/OpcPackage.mjs +123 -7
  193. package/dist/packages/WordprocessingMLPackage.d.mts +45 -8
  194. package/dist/packages/WordprocessingMLPackage.mjs +158 -21
  195. package/dist/packages/index.d.mts +1 -1
  196. package/dist/parts/DefaultXmlPart.mjs +2 -2
  197. package/dist/parts/Part.d.mts +14 -2
  198. package/dist/parts/RelationshipsPart.d.mts +15 -0
  199. package/dist/parts/RelationshipsPart.mjs +66 -1
  200. package/dist/parts/XmlPart.d.mts +63 -32
  201. package/dist/parts/XmlPart.mjs +240 -96
  202. package/dist/parts/wml/comments.mjs +30 -0
  203. package/dist/parts/wml/index.d.mts +12 -1
  204. package/dist/parts/wml/index.mjs +37 -1
  205. package/dist/xml/dom.d.mts +7 -0
  206. package/dist/xml/dom.mjs +18 -0
  207. package/package.json +2 -2
@@ -3,6 +3,7 @@
3
3
  // only, never from `.` or `./opc`, so a browser or add-in bundle never sees them.
4
4
  import { readFile, readdir, stat, mkdir, writeFile } from 'node:fs/promises';
5
5
  import { join, dirname, sep } from 'node:path';
6
+ import { EntryIndex } from './PartStore.mjs';
6
7
  import { CONTENT_TYPES_NAME } from './PartName.mjs';
7
8
  import { Docx4JException } from './exceptions.mjs';
8
9
  /**
@@ -21,8 +22,11 @@ import { Docx4JException } from './exceptions.mjs';
21
22
  export class DirectoryPartStore {
22
23
  constructor(dir) {
23
24
  this.dir = dir;
24
- /** Store name (no leading '/') to [absolute file path, size]. */
25
- this.entries = new Map();
25
+ /**
26
+ * Store name (no leading '/') to [absolute file path, size], looked up without case; of two files whose names differ only
27
+ * in case (a case-sensitive file system), the first in the walk is read and the rest counted (CR-024).
28
+ */
29
+ this.entries = new EntryIndex();
26
30
  }
27
31
  /** Scans a directory and returns a store over it. */
28
32
  static async open(dir) {
@@ -48,15 +52,18 @@ export class DirectoryPartStore {
48
52
  if (info.isDirectory())
49
53
  await this.scan(childRelative);
50
54
  else if (info.isFile())
51
- this.entries.set(childRelative, [childPath, info.size]);
55
+ this.entries.add(childRelative, [childPath, info.size]);
52
56
  }
53
57
  }
54
58
  partNames() {
55
- return this.entries.keys();
59
+ return this.entries.names();
56
60
  }
57
61
  has(partName) {
58
62
  return this.entries.has(normalize(partName));
59
63
  }
64
+ duplicates() {
65
+ return this.entries.duplicates();
66
+ }
60
67
  async load(partName) {
61
68
  const entry = this.entries.get(normalize(partName));
62
69
  if (!entry)
@@ -1,5 +1,5 @@
1
1
  import { type PackageElement } from '@docx4j/generated-objects-ts';
2
- import type { PartStore, PartSink, PutOptions } from './PartStore.mjs';
2
+ import { type PartStore, type PartSink, type PutOptions } from './PartStore.mjs';
3
3
  export declare const PKG_NS = "http://schemas.microsoft.com/office/2006/xmlPackage";
4
4
  /**
5
5
  * A flat OPC package (`pkg:package`, what Office JS `getOoxml()` returns) as a container
@@ -7,10 +7,11 @@ export declare const PKG_NS = "http://schemas.microsoft.com/office/2006/xmlPacka
7
7
  * same job is done by `FlatOpcXmlImporter`). The content types are carried per part, so
8
8
  * `[Content_Types].xml` is synthesised. XML parts are serialised back to bytes on `load`; the
9
9
  * document loaded from them is therefore not byte-identical to what Office would have zipped.
10
+ * Parts are looked up as OPC part names, without case, and of two `pkg:part`s of one name the first
11
+ * is the one read, the rest counted in `duplicates()` (CR-024).
10
12
  */
11
13
  export declare class FlatOpcPartStore implements PartStore {
12
14
  private readonly parts;
13
- private readonly order;
14
15
  private readonly cache;
15
16
  private contentTypesXml?;
16
17
  /** From the `pkg:package` string. */
@@ -22,6 +23,7 @@ export declare class FlatOpcPartStore implements PartStore {
22
23
  contentTypeOf(partName: string): string | undefined;
23
24
  partNames(): Iterable<string>;
24
25
  has(partName: string): boolean;
26
+ duplicates(): ReadonlyMap<string, number>;
25
27
  load(partName: string): Promise<Uint8Array>;
26
28
  private buildContentTypes;
27
29
  }
@@ -1,4 +1,5 @@
1
1
  import { marshalNode } from '@docx4j/generated-objects-ts';
2
+ import { EntryIndex, entryKey } from './PartStore.mjs';
2
3
  import { ContentTypeManager } from './ContentTypeManager.mjs';
3
4
  import { ContentTypes, isXmlContentType } from './ContentTypes.mjs';
4
5
  import { CONTENT_TYPES_NAME } from './PartName.mjs';
@@ -11,18 +12,18 @@ export const PKG_NS = 'http://schemas.microsoft.com/office/2006/xmlPackage';
11
12
  * same job is done by `FlatOpcXmlImporter`). The content types are carried per part, so
12
13
  * `[Content_Types].xml` is synthesised. XML parts are serialised back to bytes on `load`; the
13
14
  * document loaded from them is therefore not byte-identical to what Office would have zipped.
15
+ * Parts are looked up as OPC part names, without case, and of two `pkg:part`s of one name the first
16
+ * is the one read, the rest counted in `duplicates()` (CR-024).
14
17
  */
15
18
  export class FlatOpcPartStore {
16
19
  /** From the `pkg:package` string. */
17
20
  constructor(source) {
18
- this.parts = new Map();
19
- this.order = [];
21
+ this.parts = new EntryIndex();
20
22
  this.cache = new Map();
21
23
  if (typeof source === 'string')
22
24
  this.readString(source);
23
25
  else
24
26
  this.readPackageElement(source);
25
- this.order.unshift(CONTENT_TYPES_NAME);
26
27
  }
27
28
  readString(text) {
28
29
  var _a;
@@ -65,8 +66,7 @@ export class FlatOpcPartStore {
65
66
  }
66
67
  }
67
68
  add(entry) {
68
- this.parts.set(entry.name, entry);
69
- this.order.push(entry.name);
69
+ this.parts.add(entry.name, entry);
70
70
  }
71
71
  /** The content type of a part as carried by the flat package. */
72
72
  contentTypeOf(partName) {
@@ -74,16 +74,19 @@ export class FlatOpcPartStore {
74
74
  return (_a = this.parts.get(partName)) === null || _a === void 0 ? void 0 : _a.contentType;
75
75
  }
76
76
  partNames() {
77
- return this.order;
77
+ return [CONTENT_TYPES_NAME, ...this.parts.names()];
78
78
  }
79
79
  has(partName) {
80
80
  return partName === CONTENT_TYPES_NAME || this.parts.has(partName);
81
81
  }
82
+ duplicates() {
83
+ return this.parts.duplicates();
84
+ }
82
85
  async load(partName) {
83
86
  var _a;
84
87
  if (partName === CONTENT_TYPES_NAME)
85
88
  return ((_a = this.contentTypesXml) !== null && _a !== void 0 ? _a : (this.contentTypesXml = encodeText(this.buildContentTypes().toXml())));
86
- const cached = this.cache.get(partName);
89
+ const cached = this.cache.get(entryKey(partName));
87
90
  if (cached)
88
91
  return cached;
89
92
  const part = this.parts.get(partName);
@@ -100,7 +103,7 @@ export class FlatOpcPartStore {
100
103
  else {
101
104
  bytes = new Uint8Array(0);
102
105
  }
103
- this.cache.set(partName, bytes);
106
+ this.cache.set(entryKey(partName), bytes);
104
107
  return bytes;
105
108
  }
106
109
  buildContentTypes() {
@@ -2,6 +2,14 @@ import type { PartStore } from './PartStore.mjs';
2
2
  import { type DomPreprocessor } from '../parts/XmlPart.mjs';
3
3
  import { PartRegistry } from '../parts/PartRegistry.mjs';
4
4
  import type { OpcPackage } from '../packages/OpcPackage.mjs';
5
+ /**
6
+ * WD REQ-207's option (CR-031): how a load treats a package Word reads only through a repair, or won't open. `'refuse'`, the
7
+ * default, keeps every refusal; `'word'` follows Word's repair where the WD states one, with that repair's warning; `'all'`
8
+ * does that and makes this package's own repairs, each documented and warned (README.md, "Repairs").
9
+ */
10
+ export type RepairMode = 'refuse' | 'word' | 'all';
11
+ /** The values of `LoadOptions.repair`, in the WD's order. */
12
+ export declare const REPAIR_MODES: readonly RepairMode[];
5
13
  export interface LoadOptions {
6
14
  /** Part classes by content type; defaults to docx4j's table. */
7
15
  registry?: PartRegistry;
@@ -16,6 +24,30 @@ export interface LoadOptions {
16
24
  mcePreprocess?: boolean;
17
25
  /** Runs over every XML part's DOM before it is unmarshalled; set by the package from `mcePreprocess` (`mceKeep` or `mcePreprocess`). */
18
26
  preprocessor?: DomPreprocessor;
27
+ /**
28
+ * How a package Word reads only through a repair, or won't open, is loaded (WD REQ-207 and REQ-208; CR-031). It acts at the
29
+ * load, so it holds for every operation on the package. Any other value is refused.
30
+ *
31
+ * - `'refuse'`, the default: every refusal stands. A container holding two entries of one name, OPC part names compared
32
+ * without case, throws `DuplicateEntryException` (REQ-206, CR-024); more than one officeDocument relationship,
33
+ * `MultipleMainPartsException` (CR-024 section 9); a relationships part in another namespace or none,
34
+ * `RelationshipsNamespaceException` (CR-025 section 8); an `External` officeDocument relationship beside an internal
35
+ * one, `ExternalMainPartException` (REQ-206's NOTE).
36
+ * - `'word'`: Word's repair, each with its warning. The first entry of each duplicated name is read, in the container's
37
+ * order (a zip's central directory), the rest ignored, a `DuplicateEntryWarning` each; of several officeDocument
38
+ * relationships the **last** one's target, as a fresh Word reads it, the others dropped, one `MultipleMainPartsWarning`;
39
+ * a relationships part in a wrong namespace read by local name, a `RelationshipsNamespaceWarning`. A save writes the
40
+ * repaired package: one copy of each entry, one main part, the right namespace. `External` beside an internal
41
+ * relationship is still refused, since Word refuses it even through its repair.
42
+ * - `'all'`: `'word'`, and this package's own repairs. One today: `External` beside an internal relationship, the internal
43
+ * target read and every `External` officeDocument relationship dropped, its URL never fetched, an
44
+ * `ExternalMainPartWarning`.
45
+ *
46
+ * At every value an `External` officeDocument relationship with no internal one is refused, as a package with no main
47
+ * document part (REQ-321), and the save's own gate is left alone: `pkg.checkLossOnSave` decides whether a save may lose
48
+ * what a read skipped (CR-031 D6).
49
+ */
50
+ repair?: RepairMode;
19
51
  }
20
52
  /**
21
53
  * Loads a package from a container (docx4j io3.Load3): content types, then the package
package/dist/opc/Load.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import { PartName, CONTENT_TYPES_NAME } from './PartName.mjs';
2
2
  import { ContentTypeManager } from './ContentTypeManager.mjs';
3
- import { Docx4JException, InvalidFormatException } from './exceptions.mjs';
3
+ import { Docx4JException, DuplicateEntryException, ExternalMainPartException, InvalidFormatException, MultipleMainPartsException } from './exceptions.mjs';
4
4
  import { Namespaces } from '../parts/Namespaces.mjs';
5
5
  import { RelationshipsPart, isExternal } from '../parts/RelationshipsPart.mjs';
6
6
  import { XmlPart } from '../parts/XmlPart.mjs';
@@ -8,6 +8,8 @@ import { CustomXmlDataStoragePart } from '../parts/DefaultXmlPart.mjs';
8
8
  import { CustomXmlDataStoragePropertiesPart, DS_NS } from '../parts/customXml/index.mjs';
9
9
  import { defaultPartRegistry } from '../parts/PartRegistry.mjs';
10
10
  import { decodeXmlText, parseXml } from '../xml/dom.mjs';
11
+ /** The values of `LoadOptions.repair`, in the WD's order. */
12
+ export const REPAIR_MODES = Object.freeze(['refuse', 'word', 'all']);
11
13
  /** Relationship types whose target is the main part (docx4j PackageRelsUtil.getNameOfMainPart). */
12
14
  const MAIN_PART_RELATIONSHIPS = [
13
15
  Namespaces.DOCUMENT,
@@ -15,23 +17,38 @@ const MAIN_PART_RELATIONSHIPS = [
15
17
  Namespaces.DRAWINGML_DIAGRAM_LAYOUT,
16
18
  Namespaces.DOCUMENT_STRICT,
17
19
  ];
20
+ /**
21
+ * What REQ-206's statement counts (docx4j PackageRelsUtil.isMainPart): a transitional and a strict relationship are two. An
22
+ * External one isn't counted (CR-024 section 9.2): it names no part, so it can't be what the package reads.
23
+ */
24
+ const OFFICE_DOCUMENT_RELATIONSHIPS = [Namespaces.DOCUMENT, Namespaces.DOCUMENT_STRICT];
18
25
  /**
19
26
  * Loads a package from a container (docx4j io3.Load3): content types, then the package
20
27
  * relationships, then every part reachable through relationships, recursively. Parts are not
21
28
  * unmarshalled. `createPackage` picks the package class from the main part's content type.
22
29
  */
23
30
  export async function loadPackage(store, createPackage, options = {}) {
24
- var _a;
31
+ var _a, _b, _c;
25
32
  const registry = (_a = options.registry) !== null && _a !== void 0 ? _a : defaultPartRegistry;
33
+ const repair = repairOf(options.repair);
34
+ const followsWord = repair !== 'refuse';
35
+ const duplicateEntries = duplicateEntriesOf(store, followsWord);
26
36
  if (!store.has(CONTENT_TYPES_NAME))
27
37
  throw new InvalidFormatException(`${CONTENT_TYPES_NAME} is missing from this package`);
28
38
  const ctm = ContentTypeManager.parse(decodeXmlText(await store.load(CONTENT_TYPES_NAME)));
29
39
  const packageRels = RelationshipsPart.createPackageRels();
30
40
  if (!store.has('_rels/.rels'))
31
41
  throw new InvalidFormatException('_rels/.rels appears to be missing from this package');
42
+ packageRels.readsByLocalName = followsWord;
32
43
  packageRels.setBytes(await store.load('_rels/.rels'));
33
44
  await packageRels.getContents();
34
- const mainRel = packageRels.list.find((r) => MAIN_PART_RELATIONSHIPS.includes(r.type));
45
+ // External first, as docx4j has it: refused at refuse and word whatever else the relationships hold, and dropped at all
46
+ // before Word's last-relationship repair
47
+ const external = externalMainPart(packageRels, repair);
48
+ const mainParts = oneMainPart(packageRels, followsWord);
49
+ // an External relationship names no part, so it gives way to one that does
50
+ const mainRels = packageRels.list.filter((r) => MAIN_PART_RELATIONSHIPS.includes(r.type));
51
+ const mainRel = (_b = mainRels.find((r) => !isExternal(r))) !== null && _b !== void 0 ? _b : mainRels[0];
35
52
  if (!mainRel)
36
53
  throw new InvalidFormatException('No relationship of type officeDocument');
37
54
  const mainPartName = PartName.resolve(PartName.ROOT, mainRel.target);
@@ -39,19 +56,111 @@ export async function loadPackage(store, createPackage, options = {}) {
39
56
  pkg.contentTypeManager = ctm;
40
57
  pkg.sourcePartStore = store;
41
58
  pkg.relationshipsPart = packageRels;
59
+ pkg.duplicateEntries = duplicateEntries;
60
+ pkg.multipleMainParts = mainParts === null || mainParts === void 0 ? void 0 : mainParts.warning;
61
+ pkg.externalMainPart = external;
42
62
  packageRels.sourceP = pkg;
43
63
  packageRels.package = pkg;
44
- const loader = new Loader(pkg, store, registry, options.preprocessor);
64
+ const loader = new Loader(pkg, store, registry, options.preprocessor, followsWord);
45
65
  await loader.addPartsFromRelationships(pkg, packageRels);
46
66
  await loader.registerCustomXmlDataStorageParts();
67
+ // after the walk, so a target the kept part reaches by a relationship of its own, or a second relationship names, keeps its type
68
+ for (const name of (_c = mainParts === null || mainParts === void 0 ? void 0 : mainParts.dropped) !== null && _c !== void 0 ? _c : [])
69
+ if (!pkg.getPart(name))
70
+ ctm.removeOverrideContentType(name);
47
71
  return pkg;
48
72
  }
73
+ /**
74
+ * More than one officeDocument relationship (WD REQ-206's statement; CR-024 section 9): refused under `repair: 'refuse'`, naming
75
+ * the targets. Under `'word'` and `'all'` the last stands and the others go here, before anything is read, so that the package's class, the
76
+ * walk, `getMainPart()` and `getMainDocumentPart()` all find one main part, and a save writes one. `dropped` is the part
77
+ * names they gave, whose Overrides `loadPackage` settles after the walk.
78
+ */
79
+ function oneMainPart(rels, followsWord) {
80
+ const mains = rels.list.filter((r) => OFFICE_DOCUMENT_RELATIONSHIPS.includes(r.type) && !isExternal(r));
81
+ if (mains.length < 2)
82
+ return undefined;
83
+ const names = mains.map((r) => PartName.resolve(PartName.ROOT, r.target));
84
+ if (!followsWord)
85
+ throw new MultipleMainPartsException(names.map((name) => name.name));
86
+ for (const r of mains.slice(0, -1))
87
+ rels.removeRelationship(r);
88
+ const partName = names[names.length - 1].name;
89
+ return {
90
+ warning: {
91
+ code: 'package.multiple_main_parts',
92
+ partName,
93
+ count: mains.length,
94
+ message: `${mains.length} officeDocument relationships in this package (REQ-206); the last's target read, ${partName}`,
95
+ },
96
+ dropped: names.slice(0, -1),
97
+ };
98
+ }
99
+ /** `LoadOptions.repair`, `'refuse'` when not given; any other value refused (WD REQ-207). */
100
+ function repairOf(repair) {
101
+ if (repair === undefined)
102
+ return 'refuse';
103
+ if (!REPAIR_MODES.includes(repair)) {
104
+ throw new Docx4JException(`repair: '${String(repair)}' is not ${REPAIR_MODES.map((m) => `'${m}'`).join(', ')} (WD REQ-207)`);
105
+ }
106
+ return repair;
107
+ }
108
+ /**
109
+ * An `External` officeDocument relationship (WD REQ-206's NOTE; CR-031 section 2.2). With no internal one beside it the package
110
+ * has no main document part, refused at every value (REQ-321), where the URL used to be taken for a part name. Beside an
111
+ * internal one it's refused at `'refuse'` and `'word'`, first or last alike, since Word refuses it even through its repair;
112
+ * at `'all'` every such relationship goes here, before anything is read, so a save writes none and the URL is never
113
+ * fetched, and the warning names the part read: the last internal relationship's target, as Word's repair of several reads.
114
+ */
115
+ function externalMainPart(rels, repair) {
116
+ const mains = rels.list.filter((r) => OFFICE_DOCUMENT_RELATIONSHIPS.includes(r.type));
117
+ const externals = mains.filter(isExternal);
118
+ if (externals.length === 0)
119
+ return undefined;
120
+ const urls = externals.map((r) => r.target);
121
+ const internals = mains.filter((r) => !isExternal(r)).map((r) => PartName.resolve(PartName.ROOT, r.target).name);
122
+ if (internals.length === 0) {
123
+ throw new InvalidFormatException(`This package's only officeDocument relationship is External, to ${urls.join(', ')}, so it has no `
124
+ + 'main document part (REQ-321); refused at every value of repair. The URL is not fetched');
125
+ }
126
+ if (repair !== 'all')
127
+ throw new ExternalMainPartException(urls, internals);
128
+ for (const r of externals)
129
+ rels.removeRelationship(r);
130
+ const partName = internals[internals.length - 1];
131
+ return {
132
+ code: 'package.external_main_part',
133
+ partName,
134
+ urls,
135
+ message: `${urls.length === 1 ? 'An External officeDocument relationship' : `${urls.length} External officeDocument relationships`}, `
136
+ + `to ${urls.join(', ')}, beside the internal one dropped, ${partName} read, the URL not fetched: a repair of this package's own (REQ-207)`,
137
+ };
138
+ }
139
+ /**
140
+ * The store's duplicated names (CR-024, WD REQ-206), refused under `repair: 'refuse'` before anything is read, else one warning
141
+ * each. A store without `duplicates()` is not checked.
142
+ */
143
+ function duplicateEntriesOf(store, followsWord) {
144
+ var _a;
145
+ const duplicates = (_a = store.duplicates) === null || _a === void 0 ? void 0 : _a.call(store);
146
+ if (!duplicates || duplicates.size === 0)
147
+ return [];
148
+ if (!followsWord)
149
+ throw new DuplicateEntryException(Object.fromEntries(duplicates));
150
+ return [...duplicates].map(([entry, count]) => ({
151
+ code: 'package.duplicate_entry',
152
+ entry,
153
+ count,
154
+ message: `${entry}: ${count} entries of one name in this package; the first read, the rest ignored (REQ-206)`,
155
+ }));
156
+ }
49
157
  class Loader {
50
- constructor(pkg, store, registry, preprocessor) {
158
+ constructor(pkg, store, registry, preprocessor, followsWord) {
51
159
  this.pkg = pkg;
52
160
  this.store = store;
53
161
  this.registry = registry;
54
162
  this.preprocessor = preprocessor;
163
+ this.followsWord = followsWord;
55
164
  this.handled = new Set();
56
165
  }
57
166
  async addPartsFromRelationships(source, rp) {
@@ -88,6 +197,7 @@ class Loader {
88
197
  const relsName = PartName.relsFor(partName).storeName;
89
198
  if (this.store.has(relsName)) {
90
199
  const rrp = new RelationshipsPart(part);
200
+ rrp.readsByLocalName = this.followsWord;
91
201
  rrp.setBytes(await this.store.load(relsName));
92
202
  await rrp.getContents();
93
203
  part.relationshipsPart = rrp;
@@ -10,6 +10,35 @@ export interface PartStore {
10
10
  load(partName: string): Promise<Uint8Array>;
11
11
  /** Size if known without loading, else undefined. */
12
12
  size?(partName: string): number | undefined;
13
+ /**
14
+ * The names the container holds more than one entry of, OPC part names compared without case (WD REQ-206; CR-024): each
15
+ * first entry's spelling, with the number of entries, in container order. The load refuses a package with any, unless
16
+ * `LoadOptions.duplicateEntries` is `'first'`. Every store here has it; a store without it is not checked.
17
+ */
18
+ duplicates?(): ReadonlyMap<string, number>;
19
+ }
20
+ /** An entry's key: its name without a leading '/', lower-cased, as `PartName`'s equality compares (CR-024 section 2.1). */
21
+ export declare function entryKey(name: string): string;
22
+ /**
23
+ * A container's entries by key (`entryKey`), the first entry of each key kept with its spelling and its value and the
24
+ * rest counted: what each store reads its container into, so that `has`, `load` and `size` agree and look up without
25
+ * case, and `duplicates()` reports a name held twice (CR-024, WD REQ-206).
26
+ */
27
+ export declare class EntryIndex<T> {
28
+ private readonly byKey;
29
+ /** Adds an entry; a second entry of a key is counted, its value not kept. */
30
+ add(name: string, value: T): void;
31
+ /** Sets an entry's value, replacing the one its key holds (a sink's write), its first spelling kept. */
32
+ set(name: string, value: T): void;
33
+ get(name: string): T | undefined;
34
+ has(name: string): boolean;
35
+ delete(name: string): boolean;
36
+ /** Each key's first spelling, in the order the entries were added. */
37
+ names(): string[];
38
+ /** Each key's value (its first entry's), in the same order. */
39
+ values(): T[];
40
+ /** The keys added more than once: the first spelling and the count. */
41
+ duplicates(): Map<string, number>;
13
42
  }
14
43
  export interface PutOptions {
15
44
  /** Deflate in a zip (default true); ignored by containers that do not compress. */
@@ -23,7 +52,10 @@ export interface PartSink<R = unknown> {
23
52
  /** Completes the container. */
24
53
  finish(): Promise<R>;
25
54
  }
26
- /** An in-memory container: new packages, tests, and the result of `MemoryPartSink`. */
55
+ /**
56
+ * An in-memory container: new packages, tests, and the result of `MemoryPartSink`. The constructor's entries are read as a
57
+ * container's (a second of one name counted, `duplicates()`); `put`, a sink's write, replaces.
58
+ */
27
59
  export declare class MemoryPartStore implements PartStore, PartSink<MemoryPartStore> {
28
60
  private readonly parts;
29
61
  /** Content types as put, for stores filled through the sink side. */
@@ -33,6 +65,7 @@ export declare class MemoryPartStore implements PartStore, PartSink<MemoryPartSt
33
65
  has(partName: string): boolean;
34
66
  load(partName: string): Promise<Uint8Array>;
35
67
  size(partName: string): number | undefined;
68
+ duplicates(): ReadonlyMap<string, number>;
36
69
  put(partName: string, bytes: Uint8Array, options?: PutOptions): void;
37
70
  delete(partName: string): boolean;
38
71
  finish(): Promise<MemoryPartStore>;
@@ -1,28 +1,91 @@
1
- /** An in-memory container: new packages, tests, and the result of `MemoryPartSink`. */
1
+ /** An entry's key: its name without a leading '/', lower-cased, as `PartName`'s equality compares (CR-024 section 2.1). */
2
+ export function entryKey(name) {
3
+ return (name.startsWith('/') ? name.substring(1) : name).toLowerCase();
4
+ }
5
+ /**
6
+ * A container's entries by key (`entryKey`), the first entry of each key kept with its spelling and its value and the
7
+ * rest counted: what each store reads its container into, so that `has`, `load` and `size` agree and look up without
8
+ * case, and `duplicates()` reports a name held twice (CR-024, WD REQ-206).
9
+ */
10
+ export class EntryIndex {
11
+ constructor() {
12
+ this.byKey = new Map();
13
+ }
14
+ /** Adds an entry; a second entry of a key is counted, its value not kept. */
15
+ add(name, value) {
16
+ const key = entryKey(name);
17
+ const entry = this.byKey.get(key);
18
+ if (entry)
19
+ entry.count++;
20
+ else
21
+ this.byKey.set(key, { name: name.startsWith('/') ? name.substring(1) : name, value, count: 1 });
22
+ }
23
+ /** Sets an entry's value, replacing the one its key holds (a sink's write), its first spelling kept. */
24
+ set(name, value) {
25
+ const entry = this.byKey.get(entryKey(name));
26
+ if (entry)
27
+ entry.value = value;
28
+ else
29
+ this.add(name, value);
30
+ }
31
+ get(name) {
32
+ var _a;
33
+ return (_a = this.byKey.get(entryKey(name))) === null || _a === void 0 ? void 0 : _a.value;
34
+ }
35
+ has(name) {
36
+ return this.byKey.has(entryKey(name));
37
+ }
38
+ delete(name) {
39
+ return this.byKey.delete(entryKey(name));
40
+ }
41
+ /** Each key's first spelling, in the order the entries were added. */
42
+ names() {
43
+ return [...this.byKey.values()].map((e) => e.name);
44
+ }
45
+ /** Each key's value (its first entry's), in the same order. */
46
+ values() {
47
+ return [...this.byKey.values()].map((e) => e.value);
48
+ }
49
+ /** The keys added more than once: the first spelling and the count. */
50
+ duplicates() {
51
+ const out = new Map();
52
+ for (const e of this.byKey.values())
53
+ if (e.count > 1)
54
+ out.set(e.name, e.count);
55
+ return out;
56
+ }
57
+ }
58
+ /**
59
+ * An in-memory container: new packages, tests, and the result of `MemoryPartSink`. The constructor's entries are read as a
60
+ * container's (a second of one name counted, `duplicates()`); `put`, a sink's write, replaces.
61
+ */
2
62
  export class MemoryPartStore {
3
63
  constructor(entries) {
4
- this.parts = new Map();
64
+ this.parts = new EntryIndex();
5
65
  /** Content types as put, for stores filled through the sink side. */
6
66
  this.contentTypes = new Map();
7
67
  if (entries)
8
68
  for (const [name, bytes] of entries)
9
- this.parts.set(normalize(name), bytes);
69
+ this.parts.add(normalize(name), bytes);
10
70
  }
11
71
  partNames() {
12
- return this.parts.keys();
72
+ return this.parts.names();
13
73
  }
14
74
  has(partName) {
15
- return this.parts.has(normalize(partName));
75
+ return this.parts.has(partName);
16
76
  }
17
77
  async load(partName) {
18
- const bytes = this.parts.get(normalize(partName));
78
+ const bytes = this.parts.get(partName);
19
79
  if (bytes === undefined)
20
80
  throw new Error(`No part ${partName}`);
21
81
  return bytes;
22
82
  }
23
83
  size(partName) {
24
84
  var _a;
25
- return (_a = this.parts.get(normalize(partName))) === null || _a === void 0 ? void 0 : _a.length;
85
+ return (_a = this.parts.get(partName)) === null || _a === void 0 ? void 0 : _a.length;
86
+ }
87
+ duplicates() {
88
+ return this.parts.duplicates();
26
89
  }
27
90
  put(partName, bytes, options) {
28
91
  const name = normalize(partName);
@@ -31,7 +94,7 @@ export class MemoryPartStore {
31
94
  this.contentTypes.set(name, options.contentType);
32
95
  }
33
96
  delete(partName) {
34
- return this.parts.delete(normalize(partName));
97
+ return this.parts.delete(partName);
35
98
  }
36
99
  async finish() {
37
100
  return this;
@@ -1,5 +1,37 @@
1
1
  import type { PartSink } from './PartStore.mjs';
2
+ import type { ValueOutOfRangeWarning, MisplacedContentWarning } from './mce/preprocessor.mjs';
3
+ import type { ContentSkippedWarning } from './skipped.mjs';
2
4
  import type { OpcPackage } from '../packages/OpcPackage.mjs';
5
+ /**
6
+ * A value a save wrote in its normal form, or left out (CR-015 D2 and D8; the objects facade's normaliser, its
7
+ * CR-013 phase C, docx4j CR-061's rule): `path` from the part's root, `before` as read, `after` as written, null
8
+ * where the attribute or the element was left out. The code is this package's (CR-015 D8); docx4j-python CR-018 reports
9
+ * the same as `package.last_save.normalised`.
10
+ */
11
+ export interface ValueNormalised {
12
+ code: 'value.normalised';
13
+ partName: string;
14
+ path: string;
15
+ before: string | undefined;
16
+ after: string | null;
17
+ message: string;
18
+ }
19
+ /**
20
+ * What the last save of a package did beyond writing it: each value it normalised (CR-015 D8), each integer
21
+ * attribute value it wrote that Word 365 reads differently, written as held (CR-017 P3, E3), with
22
+ * `checkContentOnSave` off, each child it wrote where its container never admits one (CR-016 D2), each with its path,
23
+ * each item the read dropped that a changed part was written without as Word's own save writes it (CR-025 section 10), and,
24
+ * with `checkLossOnSave` off, each it was written without that Word's save keeps or nobody has measured (CR-025 D8).
25
+ */
26
+ export interface SaveReport {
27
+ normalised: ValueNormalised[];
28
+ outOfRange: ValueOutOfRangeWarning[];
29
+ misplaced: MisplacedContentWarning[];
30
+ /** Items a changed part was written without, Word's own save dropping them too (`onSave: 'dropped'`, CR-025 D14): never refused. */
31
+ dropped: ContentSkippedWarning[];
32
+ /** Items a changed part was written without under `checkLossOnSave` off (`onSave: 'lost'`). */
33
+ lost: ContentSkippedWarning[];
34
+ }
3
35
  /**
4
36
  * Writes a package to a sink (docx4j io3.Save): `[Content_Types].xml` first, then the package
5
37
  * relationships, then every part reachable through relationships, each once. Relationship parts