@docx4j/core-ts 0.1.4 → 0.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.
Files changed (48) hide show
  1. package/README.md +76 -0
  2. package/dist/model/content/Body.d.mts +36 -2
  3. package/dist/model/content/Body.mjs +79 -4
  4. package/dist/model/content/Comment.d.mts +15 -5
  5. package/dist/model/content/Comment.mjs +70 -22
  6. package/dist/model/content/List.d.mts +80 -1
  7. package/dist/model/content/List.mjs +140 -22
  8. package/dist/model/content/Paragraph.d.mts +11 -1
  9. package/dist/model/content/Paragraph.mjs +18 -7
  10. package/dist/model/content/Range.d.mts +29 -2
  11. package/dist/model/content/Range.mjs +137 -7
  12. package/dist/model/content/binder.d.mts +44 -0
  13. package/dist/model/content/binder.mjs +95 -0
  14. package/dist/model/content/comments.d.mts +19 -1
  15. package/dist/model/content/index.d.mts +6 -4
  16. package/dist/model/content/index.mjs +4 -2
  17. package/dist/model/content/search.d.mts +27 -2
  18. package/dist/model/content/search.mjs +59 -3
  19. package/dist/model/content/stylesFacade.d.mts +37 -0
  20. package/dist/model/content/stylesFacade.mjs +114 -0
  21. package/dist/model/content/tracking.d.mts +57 -2
  22. package/dist/model/content/tracking.mjs +47 -15
  23. package/dist/model/customxml/CustomXmlPartCollection.d.mts +7 -0
  24. package/dist/model/customxml/CustomXmlPartCollection.mjs +40 -6
  25. package/dist/model/customxml/xpath.d.mts +18 -3
  26. package/dist/model/customxml/xpath.mjs +18 -3
  27. package/dist/office-js/index.d.mts +3 -1
  28. package/dist/office-js/run.d.mts +12 -2
  29. package/dist/office-js/run.mjs +8 -1
  30. package/dist/office-js/shimmed.d.mts +46 -0
  31. package/dist/office-js/shimmed.mjs +1 -0
  32. package/dist/office-js/supported.generated.mjs +1 -0
  33. package/dist/opc/exceptions.d.mts +25 -0
  34. package/dist/opc/exceptions.mjs +20 -0
  35. package/dist/opc/index.d.mts +1 -1
  36. package/dist/opc/index.mjs +1 -1
  37. package/dist/opc/mce/understood.mjs +37 -8
  38. package/dist/packages/WordprocessingMLPackage.d.mts +101 -2
  39. package/dist/packages/WordprocessingMLPackage.mjs +155 -1
  40. package/dist/parts/XmlPart.d.mts +44 -0
  41. package/dist/parts/XmlPart.mjs +86 -3
  42. package/dist/parts/sml/index.d.mts +9 -8
  43. package/dist/parts/sml/index.mjs +23 -19
  44. package/dist/parts/wml/index.d.mts +10 -2
  45. package/dist/parts/wml/index.mjs +11 -3
  46. package/dist/parts/wml/spliceableStyles.d.mts +1 -0
  47. package/dist/parts/wml/spliceableStyles.mjs +122 -0
  48. package/package.json +2 -2
@@ -12,7 +12,9 @@ import { newFontSettings, themeOfSettings, } from '../model/fonts/defaultTheme.m
12
12
  import { HeaderPart, FooterPart } from '../parts/wml/index.mjs';
13
13
  import { CustomXmlPartCollection } from '../model/customxml/CustomXmlPartCollection.mjs';
14
14
  import { DefaultXPathEngine } from '../model/customxml/xpath.mjs';
15
- import { ChangeTracker } from '../model/content/tracking.mjs';
15
+ import { ChangeTracker, highestAnnotationId } from '../model/content/tracking.mjs';
16
+ import { NumberingFacade } from '../model/content/List.mjs';
17
+ import { StylesFacade } from '../model/content/stylesFacade.mjs';
16
18
  import { PropertyResolver } from '../model/properties/PropertyResolver.mjs';
17
19
  import { PropertyResolverNotCreatedException } from '../opc/exceptions.mjs';
18
20
  import { XmlPart } from '../parts/XmlPart.mjs';
@@ -67,6 +69,18 @@ export class WordprocessingMLPackage extends OpcPackage {
67
69
  this.author = { name: 'docx4j' };
68
70
  /** Set when `changeTrackingMode` was written but the settings part was not unmarshalled yet. */
69
71
  this.trackingPending = false;
72
+ /**
73
+ * The `withTracking` calls still running, oldest first, and what the package said before the
74
+ * first of them. The latest call's markup applies; when it ends, the latest one still running
75
+ * applies again, and when none is left the package is as it was (CR-002 section 31).
76
+ */
77
+ this.trackingScopes = [];
78
+ /**
79
+ * The parts `seedAnnotationIds()` has read, with what it read: `true` for a part still holding its
80
+ * source, whose bytes cannot change, and otherwise the bytes that were set, which stay the same
81
+ * array until they are set again.
82
+ */
83
+ this.seededParts = new WeakMap();
70
84
  /**
71
85
  * The package's font settings (CR-001 Phase B step 4), docx4j's `docx4j.fonts.*` properties
72
86
  * scoped to one package: `defaultTheme` says which Office theme a font reference resolves
@@ -78,6 +92,11 @@ export class WordprocessingMLPackage extends OpcPackage {
78
92
  * `mainDocumentPart.refreshFonts()`.
79
93
  */
80
94
  this.fonts = newFontSettings();
95
+ /**
96
+ * TrackingHost: a floor for the annotation id, from the WordprocessingML parts that were **not**
97
+ * unmarshalled; `seedAnnotationIds()` fills it. 0 until then (CR-002 section 28.2).
98
+ */
99
+ this.annotationIdFloor = 0;
81
100
  // --- custom XML (CR-002 phase E) ---
82
101
  /**
83
102
  * The XPath engine the custom XML views use (CR-002 section 3.6): `document.evaluate` in a
@@ -257,6 +276,8 @@ export class WordprocessingMLPackage extends OpcPackage {
257
276
  */
258
277
  get changeTrackingMode() {
259
278
  var _a;
279
+ if (this.trackingOverride !== undefined)
280
+ return this.trackingOverride;
260
281
  if (this.trackingMode !== undefined)
261
282
  return this.trackingMode;
262
283
  const settings = (_a = this.mainDocumentPart) === null || _a === void 0 ? void 0 : _a.documentSettingsPart;
@@ -267,6 +288,7 @@ export class WordprocessingMLPackage extends OpcPackage {
267
288
  set changeTrackingMode(mode) {
268
289
  this.trackingMode = mode;
269
290
  this.tracker = undefined;
291
+ this.annotationIdCounter = undefined;
270
292
  const settings = this.mainDocumentPart ? this.settingsPart() : undefined;
271
293
  if (settings === null || settings === void 0 ? void 0 : settings.isUnmarshalled) {
272
294
  writeTrackRevisions(settings.contents, mode);
@@ -275,6 +297,58 @@ export class WordprocessingMLPackage extends OpcPackage {
275
297
  else
276
298
  this.trackingPending = true;
277
299
  }
300
+ /**
301
+ * Runs `fn` with revisions recorded as `markup` says, and the document's own setting untouched:
302
+ * nothing is written to `w:trackRevisions`, no settings part is created, and the mode a later read
303
+ * reports is the one that was there before (CR-002 section 28.1, for an editor whose agent must
304
+ * record its edits as a named author in a room whose tracking setting belongs to the document).
305
+ *
306
+ * The author, the date and the mode are restored in a `finally`, so a throw from `fn` cannot leave
307
+ * the package tracking. It is asynchronous for one reason: it awaits `seedAnnotationIds()` before
308
+ * running `fn`, so a revision written inside it cannot reuse an id from a part nobody has read
309
+ * (section 28.2). `fn` itself may be synchronous.
310
+ *
311
+ * Calls nest, and calls that overlap in time end in whatever order they end without leaving the
312
+ * package tracking. But the markup is the package's for as long as a call runs, not the call's:
313
+ * while two are running, **the later one's author and mode apply to every edit**, the earlier
314
+ * one's included, because nothing here can tell which asynchronous call an edit came from. Await
315
+ * one before starting another when they must be recorded as different authors (section 31).
316
+ */
317
+ async withTracking(markup, fn) {
318
+ await this.seedAnnotationIds();
319
+ const scope = { ...markup };
320
+ if (this.trackingScopes.length === 0) {
321
+ this.beforeScopes = { author: this.author, date: this.trackedChangeDate, override: this.trackingOverride };
322
+ }
323
+ this.trackingScopes.push(scope);
324
+ this.applyTrackingScope(scope);
325
+ try {
326
+ return await fn();
327
+ }
328
+ finally {
329
+ this.trackingScopes.splice(this.trackingScopes.indexOf(scope), 1);
330
+ const latest = this.trackingScopes[this.trackingScopes.length - 1];
331
+ if (latest !== undefined)
332
+ this.applyTrackingScope(latest);
333
+ else if (this.beforeScopes !== undefined) {
334
+ this.author = this.beforeScopes.author;
335
+ this.trackedChangeDate = this.beforeScopes.date;
336
+ this.trackingOverride = this.beforeScopes.override;
337
+ this.beforeScopes = undefined;
338
+ }
339
+ }
340
+ }
341
+ /**
342
+ * One `withTracking` call's markup made the package's. The tracker is left alone: the getter makes
343
+ * another when the mode differs, and the id counter it reads is the package's, so a tracker kept
344
+ * across the call cannot reissue an id given out inside it (section 31).
345
+ */
346
+ applyTrackingScope(scope) {
347
+ var _a;
348
+ this.author = scope.author;
349
+ this.trackedChangeDate = scope.date;
350
+ this.trackingOverride = (_a = scope.mode) !== null && _a !== void 0 ? _a : 'TrackAll';
351
+ }
278
352
  /** The mode as the settings part has it, unmarshalling the part (docx4j's async counterpart of the getter). */
279
353
  async getChangeTrackingMode() {
280
354
  var _a, _b;
@@ -287,8 +361,11 @@ export class WordprocessingMLPackage extends OpcPackage {
287
361
  async setChangeTrackingMode(mode) {
288
362
  this.trackingMode = mode;
289
363
  this.tracker = undefined;
364
+ this.annotationIdCounter = undefined;
290
365
  this.trackingPending = false;
291
366
  writeTrackRevisions(await this.settingsPart().getContents(), mode);
367
+ if (mode !== 'Off')
368
+ await this.seedAnnotationIds();
292
369
  }
293
370
  /** The settings part, created (with its relationship and content type) when the document has none. */
294
371
  settingsPart() {
@@ -318,6 +395,53 @@ export class WordprocessingMLPackage extends OpcPackage {
318
395
  this.tracker = new ChangeTracker(this, mode);
319
396
  return this.tracker;
320
397
  }
398
+ /**
399
+ * The objects an XPath selects in a part of this document, through the package's engine
400
+ * (CR-006; docx4j `MainDocumentPart.getJAXBNodesViaXPath`). The part defaults to the main
401
+ * document part, which is what a `//w:p[...]` expression means.
402
+ */
403
+ async selectObjects(xpath, options = {}) {
404
+ var _a;
405
+ const part = (_a = options.part) !== null && _a !== void 0 ? _a : this.getMainDocumentPart();
406
+ return part.selectObjects(xpath, { engine: this.xpathEngine, namespaces: options.namespaces });
407
+ }
408
+ /**
409
+ * Raises `annotationIdFloor` above every annotation id in the WordprocessingML parts that are not
410
+ * unmarshalled, reading each **privately** (`readContents`, CR-001 Phase D's device), so that a
411
+ * part nobody has touched keeps its byte-for-byte round trip and still cannot have its ids reused.
412
+ *
413
+ * Awaited by `setChangeTrackingMode()` and by `withTracking()`, which is where tracking is turned
414
+ * on; a caller that uses the synchronous `changeTrackingMode` setter on a document with unread
415
+ * parts should await this itself, since `nextId()` cannot (the content API's setters are
416
+ * synchronous, which is why the floor exists at all rather than the scan being made async).
417
+ *
418
+ * A part is read once for the bytes it holds: an agent that wraps every edit in `withTracking`
419
+ * would otherwise re-read every header, footer and note part each time (section 31). Its source
420
+ * bytes cannot change; bytes that were set are read again when they are set again.
421
+ */
422
+ async seedAnnotationIds() {
423
+ for (const part of this.parts) {
424
+ if (!(part instanceof XmlPart) || part.isUnmarshalled)
425
+ continue;
426
+ if (!part.partName.name.startsWith('/word/'))
427
+ continue;
428
+ const read = part.isUntouched ? true : await part.getBytes();
429
+ if (this.seededParts.get(part) === read)
430
+ continue;
431
+ let tree;
432
+ try {
433
+ tree = await part.readContents();
434
+ }
435
+ catch {
436
+ tree = undefined;
437
+ } // an unreadable part cannot hold ids we must avoid
438
+ if (typeof tree === 'object' && tree !== null) {
439
+ this.annotationIdFloor = highestAnnotationId(tree, this.annotationIdFloor);
440
+ }
441
+ this.seededParts.set(part, read);
442
+ }
443
+ return this.annotationIdFloor;
444
+ }
321
445
  /** TrackingHost: the trees a new revision id must be above, which is every part already unmarshalled. */
322
446
  markupRoots() {
323
447
  const out = [];
@@ -385,6 +509,36 @@ export class WordprocessingMLPackage extends OpcPackage {
385
509
  all.push(await part.getBody());
386
510
  return all;
387
511
  }
512
+ /**
513
+ * The document's style definitions as a verb that needs no `Body`: `ensure(ids)` splices in a
514
+ * style the document lacks, out of the same defaults `createPackage()` writes, with what it is
515
+ * based on and linked to (CR-002 section 22.2).
516
+ */
517
+ get styles() {
518
+ var _a;
519
+ return ((_a = this.stylesFacade) !== null && _a !== void 0 ? _a : (this.stylesFacade = new StylesFacade(this)));
520
+ }
521
+ /** The styles part, created with the defaults when the document has none (CR-002 section 22.2). */
522
+ ensureStyleDefinitionsPart() {
523
+ const main = this.getMainDocumentPart();
524
+ let part = main.styleDefinitionsPart;
525
+ if (!part) {
526
+ part = new StyleDefinitionsPart();
527
+ part.setXml(DEFAULT_STYLES_XML);
528
+ main.addTargetPart(part);
529
+ }
530
+ return part;
531
+ }
532
+ /**
533
+ * The document's list definitions as verbs that need no paragraph: `newList()` and
534
+ * `restart(numId)` (CR-002 section 19). `Paragraph.startNewList()` and `List.restart()` are
535
+ * Office JS's shapes and remain; this is for a caller that holds a `w:numId` rather than a
536
+ * paragraph - an editor with its own document model, writing `w:numPr` itself.
537
+ */
538
+ get numbering() {
539
+ var _a;
540
+ return ((_a = this.numberingFacade) !== null && _a !== void 0 ? _a : (this.numberingFacade = new NumberingFacade(this)));
541
+ }
388
542
  /**
389
543
  * Office JS `document.customXmlParts`: the custom XML data storage parts as views, with
390
544
  * `add`, and docx4j's `applyBindings()` / `updateFromContentControls()` (CR-002 section 3.5).
@@ -1,6 +1,17 @@
1
1
  import { type Jsonix } from '@docx4j/generated-objects-ts';
2
2
  import { Part } from './Part.mjs';
3
3
  import { PartName } from '../opc/PartName.mjs';
4
+ import { type Bound } from '../model/content/binder.mjs';
5
+ /** What `selectObjects` needs: the engine to evaluate with, and any prefixes beyond the defaults. */
6
+ export interface SelectOptions {
7
+ /** The engine; `pkg.selectObjects` and `Body.select` pass the package's. It is readied on the snapshot. */
8
+ engine?: {
9
+ ready(context?: Node): Promise<void>;
10
+ select(expression: string, context: Node, namespaces?: Record<string, string>): Node[];
11
+ } | undefined;
12
+ /** Prefixes added to (or overriding) the objects package's own table. */
13
+ namespaces?: Record<string, string> | undefined;
14
+ }
4
15
  /** A qualified name for a part's root element. */
5
16
  export interface RootName {
6
17
  namespaceURI: string;
@@ -31,6 +42,12 @@ export declare class XmlPart<T = unknown> extends Part {
31
42
  /** True once contents were unmarshalled or set; the part is then re-marshalled on save. */
32
43
  get isUnmarshalled(): boolean;
33
44
  get isLoaded(): boolean;
45
+ /**
46
+ * True while the part holds nothing but its source: never unmarshalled, no contents or bytes set.
47
+ * Such a part is written back byte for byte, and what a private read finds in it cannot change,
48
+ * which is what lets a reader remember it (CR-002 section 31).
49
+ */
50
+ get isUntouched(): boolean;
34
51
  /** The unmarshalled contents (docx4j getJaxbElement()); throws when not yet unmarshalled. */
35
52
  get contents(): T;
36
53
  /** The root element as [name, value]; undefined until unmarshalled. */
@@ -57,8 +74,35 @@ export declare class XmlPart<T = unknown> extends Part {
57
74
  getXml(): Promise<string>;
58
75
  /** Marshalled bytes when unmarshalled or set, else the source bytes untouched. */
59
76
  getBytes(): Promise<Uint8Array>;
77
+ /**
78
+ * The objects an XPath selects in this part, through a DOM marshalled for the purpose
79
+ * (CR-006; docx4j `JaxbXmlPart.getJAXBNodesViaXPath`, which does it with a JAXB `Binder`).
80
+ *
81
+ * Each result carries the node the expression selected, the tree object behind it, the attribute
82
+ * name when an attribute was selected, and the node's path from the root. The DOM is a **snapshot**:
83
+ * changing it changes nothing, and a select leaves the part's own bytes alone - the marshal is
84
+ * thrown away. Every call marshals afresh, since the tree has no change log a cached snapshot could
85
+ * be checked against (CR-006 section 2).
86
+ *
87
+ * The prefixes default to the ones the objects package writes, so `//w:p[w:pPr/w:pStyle/@w:val='Heading1']`
88
+ * needs no mapping; `options.namespaces` adds to or overrides them. The engine is the caller's
89
+ * (`pkg.xpathEngine`: XPath 1.0 by default, 3.1 with `FontoXPathEngine`), and is readied here on
90
+ * the snapshot itself: whether an engine can evaluate a tree is a property of the document that
91
+ * tree is in (CR-002 section 21), and readying it on a marshal of its own would cost a second one
92
+ * (CR-002 section 31).
93
+ */
94
+ selectObjects(xpath: string, options?: SelectOptions): Promise<Bound[]>;
60
95
  /** The root element as a DOM element (docx4j XmlUtils.marshaltoW3CDomDocument). */
61
96
  marshalToNode(): Promise<Element>;
97
+ /**
98
+ * Unmarshals, and names the part when the runtime refuses. The runtime's own message says what is
99
+ * wrong and, since `@docx4j/jsonix` 3.4.0, where - `w:sz/@w:val (line 12): Argument [NaN] must be
100
+ * an integer` - but not which part it was reading, which is the first thing a person editing a
101
+ * part's XML needs (CR-002 section 27). The location it found is lifted onto the exception so a
102
+ * caller does not parse the message; `line` is undefined where the parser supplies no position,
103
+ * which is every browser (only xmldom carries a locator).
104
+ */
105
+ private unmarshal;
62
106
  /** The source root element's namespace declarations, prefix to URI (`xmlns=` is the empty prefix). */
63
107
  private recordDeclarations;
64
108
  /**
@@ -1,9 +1,10 @@
1
1
  import { unmarshalNode, marshalNode, NAMESPACE_PREFIXES } from '@docx4j/generated-objects-ts';
2
2
  import { Part } from './Part.mjs';
3
3
  import { Namespaces } from './Namespaces.mjs';
4
- import { Docx4JException } from '../opc/exceptions.mjs';
4
+ import { Docx4JException, PartUnmarshalException } from '../opc/exceptions.mjs';
5
5
  import { parseXml, serializeXml, encodeText, decodeXmlText, XML_DECLARATION } from '../xml/dom.mjs';
6
6
  import { log } from '../model/properties/log.mjs';
7
+ import { bindSnapshot, boundsOf } from '../model/content/binder.mjs';
7
8
  const XMLNS_NS = 'http://www.w3.org/2000/xmlns/';
8
9
  const MC_NS = Namespaces.MARKUP_COMPATIBILITY;
9
10
  /** The objects package's prefix table the other way round, prefix to URI, built once. */
@@ -18,6 +19,26 @@ function knownUriOf(prefix) {
18
19
  return prefixToUri.get(prefix);
19
20
  }
20
21
  const warnedPrefixes = new Set();
22
+ /**
23
+ * The element or attribute a located runtime error names, out of its message: jsonix writes
24
+ * `w:sz/@w:val (line 7): ...` when it could place the fault and `w:bogus (line 3): ...` for an
25
+ * element, so the location is everything before the position or the colon - and `w:sz` must not be
26
+ * cut at its own prefix separator, which a naive split on `:` does.
27
+ */
28
+ function locationOf(message, located) {
29
+ var _a;
30
+ if (!located)
31
+ return undefined;
32
+ return (_a = /^(.+?)(?: \(line \d+(?:, column \d+)?\))?: /.exec(message)) === null || _a === void 0 ? void 0 : _a[1];
33
+ }
34
+ /** The objects package's prefix table as XPath wants it, prefix to URI; built once. */
35
+ const NAMESPACE_PREFIXES_BY_PREFIX = (() => {
36
+ const out = {};
37
+ for (const [uri, prefix] of Object.entries(NAMESPACE_PREFIXES))
38
+ if (prefix !== '' && !(prefix in out))
39
+ out[prefix] = uri;
40
+ return out;
41
+ })();
21
42
  /**
22
43
  * A part whose content is a typed element of the object model (docx4j JaxbXmlPart<E>). `T` is the
23
44
  * root element's value type (`Document`, `Styles`, ...). Contents are unmarshalled on first
@@ -36,6 +57,14 @@ export class XmlPart extends Part {
36
57
  get isLoaded() {
37
58
  return this.element !== undefined || this.bytes !== undefined;
38
59
  }
60
+ /**
61
+ * True while the part holds nothing but its source: never unmarshalled, no contents or bytes set.
62
+ * Such a part is written back byte for byte, and what a private read finds in it cannot change,
63
+ * which is what lets a reader remember it (CR-002 section 31).
64
+ */
65
+ get isUntouched() {
66
+ return this.element === undefined && this.bytes === undefined;
67
+ }
39
68
  /** The unmarshalled contents (docx4j getJaxbElement()); throws when not yet unmarshalled. */
40
69
  get contents() {
41
70
  if (this.element === undefined)
@@ -56,7 +85,7 @@ export class XmlPart extends Part {
56
85
  const doc = parseXml(decodeXmlText(bytes));
57
86
  this.recordDeclarations(doc);
58
87
  (_b = this.preprocessor) === null || _b === void 0 ? void 0 : _b.call(this, doc, this);
59
- this.element = await unmarshalNode(doc);
88
+ this.element = await this.unmarshal(doc);
60
89
  (_c = this.rootName) !== null && _c !== void 0 ? _c : (this.rootName = { namespaceURI: this.element.name.namespaceURI, localPart: this.element.name.localPart });
61
90
  this.bytes = undefined;
62
91
  }
@@ -81,7 +110,7 @@ export class XmlPart extends Part {
81
110
  const doc = parseXml(decodeXmlText(bytes));
82
111
  this.recordDeclarations(doc);
83
112
  (_b = this.preprocessor) === null || _b === void 0 ? void 0 : _b.call(this, doc, this);
84
- return (await unmarshalNode(doc)).value;
113
+ return (await this.unmarshal(doc)).value;
85
114
  }
86
115
  /** Replaces the contents (docx4j setJaxbElement / setContents). The root name defaults to the part's. */
87
116
  setContents(value, rootName) {
@@ -116,6 +145,36 @@ export class XmlPart extends Part {
116
145
  throw new Docx4JException(`Part ${this.partName} has no content and no source container`);
117
146
  return loaded;
118
147
  }
148
+ /**
149
+ * The objects an XPath selects in this part, through a DOM marshalled for the purpose
150
+ * (CR-006; docx4j `JaxbXmlPart.getJAXBNodesViaXPath`, which does it with a JAXB `Binder`).
151
+ *
152
+ * Each result carries the node the expression selected, the tree object behind it, the attribute
153
+ * name when an attribute was selected, and the node's path from the root. The DOM is a **snapshot**:
154
+ * changing it changes nothing, and a select leaves the part's own bytes alone - the marshal is
155
+ * thrown away. Every call marshals afresh, since the tree has no change log a cached snapshot could
156
+ * be checked against (CR-006 section 2).
157
+ *
158
+ * The prefixes default to the ones the objects package writes, so `//w:p[w:pPr/w:pStyle/@w:val='Heading1']`
159
+ * needs no mapping; `options.namespaces` adds to or overrides them. The engine is the caller's
160
+ * (`pkg.xpathEngine`: XPath 1.0 by default, 3.1 with `FontoXPathEngine`), and is readied here on
161
+ * the snapshot itself: whether an engine can evaluate a tree is a property of the document that
162
+ * tree is in (CR-002 section 21), and readying it on a marshal of its own would cost a second one
163
+ * (CR-002 section 31).
164
+ */
165
+ async selectObjects(xpath, options = {}) {
166
+ var _a;
167
+ const element = (_a = this.element) !== null && _a !== void 0 ? _a : (await this.getContents(), this.element);
168
+ if (element === undefined)
169
+ throw new Docx4JException(`Part ${this.partName} has no contents to select over`);
170
+ const engine = options.engine;
171
+ if (engine === undefined)
172
+ throw new Docx4JException(`selectObjects on ${this.partName} needs an XPath engine; pkg.selectObjects passes the package's`);
173
+ const snapshot = await bindSnapshot(element);
174
+ await engine.ready(snapshot.root);
175
+ const namespaces = { ...NAMESPACE_PREFIXES_BY_PREFIX, ...options.namespaces };
176
+ return boundsOf(engine.select(xpath, snapshot.root, namespaces), snapshot);
177
+ }
119
178
  /** The root element as a DOM element (docx4j XmlUtils.marshaltoW3CDomDocument). */
120
179
  async marshalToNode() {
121
180
  if (this.element === undefined)
@@ -124,6 +183,30 @@ export class XmlPart extends Part {
124
183
  this.declareIgnorablePrefixes(root);
125
184
  return root;
126
185
  }
186
+ /**
187
+ * Unmarshals, and names the part when the runtime refuses. The runtime's own message says what is
188
+ * wrong and, since `@docx4j/jsonix` 3.4.0, where - `w:sz/@w:val (line 12): Argument [NaN] must be
189
+ * an integer` - but not which part it was reading, which is the first thing a person editing a
190
+ * part's XML needs (CR-002 section 27). The location it found is lifted onto the exception so a
191
+ * caller does not parse the message; `line` is undefined where the parser supplies no position,
192
+ * which is every browser (only xmldom carries a locator).
193
+ */
194
+ async unmarshal(doc) {
195
+ var _a, _b;
196
+ try {
197
+ return await unmarshalNode(doc);
198
+ }
199
+ catch (cause) {
200
+ const located = cause;
201
+ const message = typeof (located === null || located === void 0 ? void 0 : located.message) === 'string' ? located.message : String(cause);
202
+ throw new PartUnmarshalException(`${this.partName}: ${message}`, this.partName.name, {
203
+ cause,
204
+ location: locationOf(message, (located === null || located === void 0 ? void 0 : located.jsonixLocated) === true),
205
+ line: typeof ((_a = located === null || located === void 0 ? void 0 : located.node) === null || _a === void 0 ? void 0 : _a.lineNumber) === 'number' ? located.node.lineNumber : undefined,
206
+ column: typeof ((_b = located === null || located === void 0 ? void 0 : located.node) === null || _b === void 0 ? void 0 : _b.columnNumber) === 'number' ? located.node.columnNumber : undefined,
207
+ });
208
+ }
209
+ }
127
210
  /** The source root element's namespace declarations, prefix to URI (`xmlns=` is the empty prefix). */
128
211
  recordDeclarations(doc) {
129
212
  var _a;
@@ -1,6 +1,7 @@
1
1
  import type * as sml from '@docx4j/generated-objects-ts/modules/org_xlsx4j_sml';
2
+ import type * as x14 from '@docx4j/generated-objects-ts/modules/org_xlsx4j_com_microsoft_schemas_office_spreadsheetml_x2009_x9_main';
3
+ import type * as x15 from '@docx4j/generated-objects-ts/modules/org_xlsx4j_com_microsoft_schemas_office_spreadsheetml_x2010_x11_main';
2
4
  import { XmlPart } from '../XmlPart.mjs';
3
- import { DefaultXmlPart } from '../DefaultXmlPart.mjs';
4
5
  import { BinaryPart } from '../BinaryPart.mjs';
5
6
  import { Part } from '../Part.mjs';
6
7
  import { PartName } from '../../opc/PartName.mjs';
@@ -99,31 +100,31 @@ export declare class PrinterSettingsPart extends BinaryPart {
99
100
  constructor(partName: PartName | string);
100
101
  }
101
102
  /** A slicer cache (`x14:slicerCacheDefinition`), from the workbook. docx4j `SlicerCachePart`. */
102
- export declare class SlicerCachePart extends DefaultXmlPart {
103
+ export declare class SlicerCachePart extends XmlPart<x14.CTSlicerCacheDefinition> {
103
104
  constructor(partName?: PartName | string);
104
105
  }
105
106
  /** A sheet's slicers (`x14:slicers`). docx4j `SlicersPart`. */
106
- export declare class SlicersPart extends DefaultXmlPart {
107
+ export declare class SlicersPart extends XmlPart<x14.CTSlicers> {
107
108
  constructor(partName?: PartName | string);
108
109
  }
109
110
  /** A timeline cache (`x15:timelineCacheDefinition`), from the workbook. docx4j `TimelineCachePart`. */
110
- export declare class TimelineCachePart extends DefaultXmlPart {
111
+ export declare class TimelineCachePart extends XmlPart<x15.CTTimelineCacheDefinition> {
111
112
  constructor(partName?: PartName | string);
112
113
  }
113
114
  /** A sheet's timelines (`x15:timelines`). docx4j `TimelinesPart`. */
114
- export declare class TimelinesPart extends DefaultXmlPart {
115
+ export declare class TimelinesPart extends XmlPart<x15.CTTimelines> {
115
116
  constructor(partName?: PartName | string);
116
117
  }
117
118
  /** A form control's properties (`x14:formControlPr`), from a worksheet. docx4j `ControlPropertiesPart`. */
118
- export declare class ControlPropertiesPart extends DefaultXmlPart {
119
+ export declare class ControlPropertiesPart extends XmlPart<x14.CTFormControlPr> {
119
120
  constructor(partName?: PartName | string);
120
121
  }
121
122
  /** The properties of a custom data part (`x14:datastoreItem`). docx4j `CustomDataPropertiesPart`. */
122
- export declare class CustomDataPropertiesPart extends DefaultXmlPart {
123
+ export declare class CustomDataPropertiesPart extends XmlPart<x14.CTDatastoreItem> {
123
124
  constructor(partName?: PartName | string);
124
125
  }
125
126
  /** A survey (`x15:survey`), from the workbook. docx4j `SurveyPart`. */
126
- export declare class SurveyPart extends DefaultXmlPart {
127
+ export declare class SurveyPart extends XmlPart<x15.CTSurvey> {
127
128
  constructor(partName?: PartName | string);
128
129
  }
129
130
  /**
@@ -1,10 +1,13 @@
1
1
  import { XmlPart } from '../XmlPart.mjs';
2
- import { DefaultXmlPart } from '../DefaultXmlPart.mjs';
3
2
  import { BinaryPart } from '../BinaryPart.mjs';
4
3
  import { ContentTypes } from '../../opc/ContentTypes.mjs';
5
4
  import { Namespaces } from '../Namespaces.mjs';
6
5
  import { Xlsx4jException } from '../../opc/exceptions.mjs';
7
6
  const S = 'http://schemas.openxmlformats.org/spreadsheetml/2006/main';
7
+ /** The Excel 2010 extension namespace (x14; docx4j's org.xlsx4j...x2009.x9.main). */
8
+ const X14 = 'http://schemas.microsoft.com/office/spreadsheetml/2009/9/main';
9
+ /** The Excel 2013 extension namespace (x15; docx4j's org.xlsx4j...x2010.x11.main). */
10
+ const X15 = 'http://schemas.microsoft.com/office/spreadsheetml/2010/11/main';
8
11
  export class WorkbookPart extends XmlPart {
9
12
  constructor(partName = '/xl/workbook.xml', contentType = ContentTypes.SPREADSHEETML_WORKBOOK) {
10
13
  super(partName, contentType, Namespaces.SPREADSHEETML_WORKBOOK, { namespaceURI: S, localPart: 'workbook' });
@@ -196,50 +199,51 @@ export class PrinterSettingsPart extends BinaryPart {
196
199
  super(partName, ContentTypes.SPREADSHEETML_PRINTER_SETTINGS, Namespaces.SPREADSHEETML_PRINTER_SETTINGS);
197
200
  }
198
201
  }
199
- // The Excel 2010 and 2013 extension parts (CR-004 phase A; docx4j
200
- // org.docx4j.openpackaging.parts.SpreadsheetML). The seven XML ones are `DefaultXmlPart`s for
201
- // now: their DOM round-trips byte for byte, and nothing re-marshals them, so an `mc:Ignorable`
202
- // they carry is untouched. Phase B makes them `XmlPart`s over the `x14` and `x15` roots.
202
+ // The Excel 2010 and 2013 extension parts (CR-004; docx4j
203
+ // org.docx4j.openpackaging.parts.SpreadsheetML). Phase A held the seven XML ones as DOM; phase B
204
+ // types them over the x14 and x15 roots the objects package's CR-022 regeneration bound, so their
205
+ // contents read like any other part's and a re-marshalled root is Excel's own shape (the
206
+ // mc:Ignorable Excel writes on them is a property of each root, re-declared by `XmlPart`).
203
207
  /** A slicer cache (`x14:slicerCacheDefinition`), from the workbook. docx4j `SlicerCachePart`. */
204
- export class SlicerCachePart extends DefaultXmlPart {
208
+ export class SlicerCachePart extends XmlPart {
205
209
  constructor(partName = '/xl/slicerCaches/slicerCache1.xml') {
206
- super(partName, ContentTypes.SPREADSHEETML_SLICER_CACHE, Namespaces.SPREADSHEETML_SLICER_CACHE);
210
+ super(partName, ContentTypes.SPREADSHEETML_SLICER_CACHE, Namespaces.SPREADSHEETML_SLICER_CACHE, { namespaceURI: X14, localPart: 'slicerCacheDefinition' });
207
211
  }
208
212
  }
209
213
  /** A sheet's slicers (`x14:slicers`). docx4j `SlicersPart`. */
210
- export class SlicersPart extends DefaultXmlPart {
214
+ export class SlicersPart extends XmlPart {
211
215
  constructor(partName = '/xl/slicers/slicer1.xml') {
212
- super(partName, ContentTypes.SPREADSHEETML_SLICERS, Namespaces.SPREADSHEETML_SLICERS);
216
+ super(partName, ContentTypes.SPREADSHEETML_SLICERS, Namespaces.SPREADSHEETML_SLICERS, { namespaceURI: X14, localPart: 'slicers' });
213
217
  }
214
218
  }
215
219
  /** A timeline cache (`x15:timelineCacheDefinition`), from the workbook. docx4j `TimelineCachePart`. */
216
- export class TimelineCachePart extends DefaultXmlPart {
220
+ export class TimelineCachePart extends XmlPart {
217
221
  constructor(partName = '/xl/timelineCaches/timelineCache1.xml') {
218
- super(partName, ContentTypes.SPREADSHEETML_TIMELINE_CACHE, Namespaces.SPREADSHEETML_TIMELINE_CACHE);
222
+ super(partName, ContentTypes.SPREADSHEETML_TIMELINE_CACHE, Namespaces.SPREADSHEETML_TIMELINE_CACHE, { namespaceURI: X15, localPart: 'timelineCacheDefinition' });
219
223
  }
220
224
  }
221
225
  /** A sheet's timelines (`x15:timelines`). docx4j `TimelinesPart`. */
222
- export class TimelinesPart extends DefaultXmlPart {
226
+ export class TimelinesPart extends XmlPart {
223
227
  constructor(partName = '/xl/timelines/timeline1.xml') {
224
- super(partName, ContentTypes.SPREADSHEETML_TIMELINES, Namespaces.SPREADSHEETML_TIMELINES);
228
+ super(partName, ContentTypes.SPREADSHEETML_TIMELINES, Namespaces.SPREADSHEETML_TIMELINES, { namespaceURI: X15, localPart: 'timelines' });
225
229
  }
226
230
  }
227
231
  /** A form control's properties (`x14:formControlPr`), from a worksheet. docx4j `ControlPropertiesPart`. */
228
- export class ControlPropertiesPart extends DefaultXmlPart {
232
+ export class ControlPropertiesPart extends XmlPart {
229
233
  constructor(partName = '/xl/ctrlProps/ctrlProp1.xml') {
230
- super(partName, ContentTypes.SPREADSHEETML_CONTROL_PROPERTIES, Namespaces.SPREADSHEETML_CONTROL_PROPERTIES);
234
+ super(partName, ContentTypes.SPREADSHEETML_CONTROL_PROPERTIES, Namespaces.SPREADSHEETML_CONTROL_PROPERTIES, { namespaceURI: X14, localPart: 'formControlPr' });
231
235
  }
232
236
  }
233
237
  /** The properties of a custom data part (`x14:datastoreItem`). docx4j `CustomDataPropertiesPart`. */
234
- export class CustomDataPropertiesPart extends DefaultXmlPart {
238
+ export class CustomDataPropertiesPart extends XmlPart {
235
239
  constructor(partName = '/xl/customDataProps/customDataProps1.xml') {
236
- super(partName, ContentTypes.SPREADSHEETML_CUSTOM_DATA_PROPERTIES, Namespaces.SPREADSHEETML_CUSTOM_DATA_PROPERTIES);
240
+ super(partName, ContentTypes.SPREADSHEETML_CUSTOM_DATA_PROPERTIES, Namespaces.SPREADSHEETML_CUSTOM_DATA_PROPERTIES, { namespaceURI: X14, localPart: 'datastoreItem' });
237
241
  }
238
242
  }
239
243
  /** A survey (`x15:survey`), from the workbook. docx4j `SurveyPart`. */
240
- export class SurveyPart extends DefaultXmlPart {
244
+ export class SurveyPart extends XmlPart {
241
245
  constructor(partName = '/xl/surveys/survey1.xml') {
242
- super(partName, ContentTypes.SPREADSHEETML_SURVEY, Namespaces.SPREADSHEETML_SURVEY);
246
+ super(partName, ContentTypes.SPREADSHEETML_SURVEY, Namespaces.SPREADSHEETML_SURVEY, { namespaceURI: X15, localPart: 'survey' });
243
247
  }
244
248
  }
245
249
  /**
@@ -85,8 +85,16 @@ export declare class MainDocumentPart extends DocumentPart<wml.Document> {
85
85
  /** The style ids used directly in the document (docx4j `getStylesInUse()`); not those that
86
86
  * others are merely based on. */
87
87
  getStylesInUse(): Promise<Set<string>>;
88
- /** The block-level content of each story, in docx4j's order: the body, then the headers and
89
- * footers, the endnotes, the footnotes and the comments. */
88
+ /**
89
+ * The block-level content of each story, in docx4j's order: the body, then the headers and
90
+ * footers, the endnotes, the footnotes and the comments.
91
+ *
92
+ * Every story is read **privately** (`readContents`, which answers the unmarshalled tree when
93
+ * there is one and parses without keeping it when there is not), so asking a document which
94
+ * fonts it uses does not cost any part its byte-for-byte round trip. The body used to come from
95
+ * `getContents()`, which unmarshalled the main part and so re-marshalled it on the next save
96
+ * (CR-001 section 18).
97
+ */
90
98
  private stories;
91
99
  /** The styles part and its `w:default="1"` style ids, read without costing it its round trip. */
92
100
  private styleSource;
@@ -207,11 +207,19 @@ export class MainDocumentPart extends DocumentPart {
207
207
  async getStylesInUse() {
208
208
  return walkStories(await this.stories(), await this.styleSource(), undefined, false).styles;
209
209
  }
210
- /** The block-level content of each story, in docx4j's order: the body, then the headers and
211
- * footers, the endnotes, the footnotes and the comments. */
210
+ /**
211
+ * The block-level content of each story, in docx4j's order: the body, then the headers and
212
+ * footers, the endnotes, the footnotes and the comments.
213
+ *
214
+ * Every story is read **privately** (`readContents`, which answers the unmarshalled tree when
215
+ * there is one and parses without keeping it when there is not), so asking a document which
216
+ * fonts it uses does not cost any part its byte-for-byte round trip. The body used to come from
217
+ * `getContents()`, which unmarshalled the main part and so re-marshalled it on the next save
218
+ * (CR-001 section 18).
219
+ */
212
220
  async stories() {
213
221
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
214
- const out = [(_b = (_a = (await this.getContents()).body) === null || _a === void 0 ? void 0 : _a.content) !== null && _b !== void 0 ? _b : []];
222
+ const out = [(_b = (_a = (await this.readContents()).body) === null || _a === void 0 ? void 0 : _a.content) !== null && _b !== void 0 ? _b : []];
215
223
  const rp = this.relationshipsPart;
216
224
  for (const r of (_c = rp === null || rp === void 0 ? void 0 : rp.relationships.relationship) !== null && _c !== void 0 ? _c : []) {
217
225
  const part = rp === null || rp === void 0 ? void 0 : rp.getPart(r);
@@ -0,0 +1 @@
1
+ export declare const SPLICEABLE_STYLES_XML = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<w:styles xmlns:r=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships\" xmlns:w=\"http://schemas.openxmlformats.org/wordprocessingml/2006/main\">\n<w:style w:type=\"paragraph\" w:styleId=\"BlockText\">\n <w:name w:val=\"Block Text\" />\n <w:basedOn w:val=\"Normal\" />\n <w:uiPriority w:val=\"99\" />\n <w:unhideWhenUsed />\n <w:rsid w:val=\"00841CD9\" />\n <w:pPr>\n <w:pBdr>\n <w:top w:val=\"single\" w:sz=\"2\" w:space=\"10\" w:color=\"4F81BD\" w:themeColor=\"accent1\" w:shadow=\"1\" />\n <w:left w:val=\"single\" w:sz=\"2\" w:space=\"10\" w:color=\"4F81BD\" w:themeColor=\"accent1\" w:shadow=\"1\" />\n <w:bottom w:val=\"single\" w:sz=\"2\" w:space=\"10\" w:color=\"4F81BD\" w:themeColor=\"accent1\" w:shadow=\"1\" />\n <w:right w:val=\"single\" w:sz=\"2\" w:space=\"10\" w:color=\"4F81BD\" w:themeColor=\"accent1\" w:shadow=\"1\" />\n </w:pBdr>\n <w:ind w:left=\"1152\" w:right=\"1152\" />\n </w:pPr>\n <w:rPr>\n <w:rFonts w:eastAsiaTheme=\"minorEastAsia\" />\n <w:i />\n <w:iCs />\n <w:color w:val=\"4F81BD\" w:themeColor=\"accent1\" />\n </w:rPr>\n </w:style>\n <w:style w:type=\"paragraph\" w:styleId=\"EndnoteText\">\n <w:name w:val=\"endnote text\" />\n <w:basedOn w:val=\"Normal\" />\n <w:link w:val=\"EndnoteTextChar\" />\n <w:uiPriority w:val=\"99\" />\n <w:unhideWhenUsed />\n <w:rsid w:val=\"00841CD9\" />\n <w:rPr>\n <w:sz w:val=\"20\" />\n <w:szCs w:val=\"20\" />\n </w:rPr>\n </w:style>\n <w:style w:type=\"character\" w:customStyle=\"1\" w:styleId=\"EndnoteTextChar\">\n <w:name w:val=\"Endnote Text Char\" />\n <w:basedOn w:val=\"DefaultParagraphFont\" />\n <w:link w:val=\"EndnoteText\" />\n <w:uiPriority w:val=\"99\" />\n <w:rsid w:val=\"00841CD9\" />\n <w:rPr>\n <w:sz w:val=\"20\" />\n <w:szCs w:val=\"20\" />\n </w:rPr>\n </w:style>\n <w:style w:type=\"paragraph\" w:styleId=\"Footer\">\n <w:name w:val=\"footer\" />\n <w:basedOn w:val=\"Normal\" />\n <w:link w:val=\"FooterChar\" />\n <w:uiPriority w:val=\"99\" />\n <w:unhideWhenUsed />\n <w:rsid w:val=\"00841CD9\" />\n <w:pPr>\n <w:tabs>\n <w:tab w:val=\"center\" w:pos=\"4680\" />\n <w:tab w:val=\"right\" w:pos=\"9360\" />\n </w:tabs>\n </w:pPr>\n </w:style>\n <w:style w:type=\"character\" w:customStyle=\"1\" w:styleId=\"FooterChar\">\n <w:name w:val=\"Footer Char\" />\n <w:basedOn w:val=\"DefaultParagraphFont\" />\n <w:link w:val=\"Footer\" />\n <w:uiPriority w:val=\"99\" />\n <w:rsid w:val=\"00841CD9\" />\n </w:style>\n <w:style w:type=\"paragraph\" w:styleId=\"FootnoteText\">\n <w:name w:val=\"footnote text\" />\n <w:basedOn w:val=\"Normal\" />\n <w:link w:val=\"FootnoteTextChar\" />\n <w:uiPriority w:val=\"99\" />\n <w:unhideWhenUsed />\n <w:rsid w:val=\"00841CD9\" />\n <w:rPr>\n <w:sz w:val=\"20\" />\n <w:szCs w:val=\"20\" />\n </w:rPr>\n </w:style>\n <w:style w:type=\"character\" w:customStyle=\"1\" w:styleId=\"FootnoteTextChar\">\n <w:name w:val=\"Footnote Text Char\" />\n <w:basedOn w:val=\"DefaultParagraphFont\" />\n <w:link w:val=\"FootnoteText\" />\n <w:uiPriority w:val=\"99\" />\n <w:rsid w:val=\"00841CD9\" />\n <w:rPr>\n <w:sz w:val=\"20\" />\n <w:szCs w:val=\"20\" />\n </w:rPr>\n </w:style>\n<w:style w:type=\"character\" w:styleId=\"FootnoteReference\">\n <w:name w:val=\"footnote reference\" />\n <w:basedOn w:val=\"DefaultParagraphFont\" />\n <w:uiPriority w:val=\"99\" />\n <w:unhideWhenUsed />\n <w:rsid w:val=\"00D1197D\" />\n <w:rPr>\n <w:vertAlign w:val=\"superscript\" />\n </w:rPr>\n </w:style>\n <w:style w:type=\"character\" w:styleId=\"EndnoteReference\">\n <w:name w:val=\"endnote reference\" />\n <w:basedOn w:val=\"DefaultParagraphFont\" />\n <w:uiPriority w:val=\"99\" />\n <w:unhideWhenUsed />\n <w:rsid w:val=\"00D1197D\" />\n <w:rPr>\n <w:vertAlign w:val=\"superscript\" />\n </w:rPr>\n </w:style>\n</w:styles>";