@docx4j/core-ts 0.3.0 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/model/content/Body.mjs +40 -19
- package/dist/model/content/Comment.d.mts +6 -0
- package/dist/model/content/Comment.mjs +76 -22
- package/dist/model/content/ContentControl.d.mts +11 -1
- package/dist/model/content/ContentControl.mjs +69 -10
- package/dist/model/content/Paragraph.d.mts +37 -2
- package/dist/model/content/Paragraph.mjs +140 -38
- package/dist/model/content/Range.mjs +49 -5
- package/dist/model/content/Table.mjs +6 -8
- package/dist/model/content/TrackedChange.d.mts +32 -4
- package/dist/model/content/TrackedChange.mjs +271 -34
- package/dist/model/content/index.d.mts +1 -1
- package/dist/model/content/index.mjs +1 -1
- package/dist/model/content/stylesFacade.mjs +9 -3
- package/dist/model/content/tracking.d.mts +65 -5
- package/dist/model/content/tracking.mjs +170 -43
- package/dist/model/customxml/XmlMapping.d.mts +20 -4
- package/dist/model/customxml/XmlMapping.mjs +44 -6
- package/dist/model/customxml/bindings.d.mts +9 -2
- package/dist/model/customxml/bindings.mjs +18 -2
- package/dist/model/customxml/index.d.mts +1 -1
- package/dist/model/customxml/index.mjs +1 -1
- package/dist/model/customxml/insert.d.mts +10 -0
- package/dist/model/customxml/insert.mjs +13 -0
- package/dist/model/fonts/RunFontSelector.d.mts +8 -1
- package/dist/model/fonts/RunFontSelector.mjs +6 -5
- package/dist/model/properties/PropertyResolver.d.mts +110 -10
- package/dist/model/properties/PropertyResolver.mjs +300 -31
- package/dist/model/properties/index.d.mts +3 -0
- package/dist/model/properties/index.mjs +2 -0
- package/dist/model/properties/table.d.mts +155 -0
- package/dist/model/properties/table.mjs +315 -0
- package/dist/model/properties/tableStyleConditions.d.mts +101 -0
- package/dist/model/properties/tableStyleConditions.mjs +296 -0
- package/dist/packages/WordprocessingMLPackage.d.mts +6 -0
- package/dist/packages/WordprocessingMLPackage.mjs +11 -0
- package/dist/parts/wml/commentStyles.d.mts +3 -0
- package/dist/parts/wml/commentStyles.mjs +25 -0
- package/dist/parts/wml/comments.mjs +4 -22
- package/dist/parts/wml/placeholderStyle.d.mts +3 -0
- package/dist/parts/wml/placeholderStyle.mjs +13 -0
- package/package.json +2 -2
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { deepCopy, deepCopyAsSync } from '@docx4j/generated-objects-ts';
|
|
2
|
-
import
|
|
2
|
+
import * as el from '@docx4j/generated-objects-ts/el/org_docx4j_wml';
|
|
3
|
+
import { createCTTrackChange, createCTRPrChange, createCTRPrChangeRPr, createCTPPrChange, createCTMarkup, createDelText, createPPr, createParaRPr, createTrPr, } from '@docx4j/generated-objects-ts/factory/org_docx4j_wml';
|
|
3
4
|
import { rPrToElements, rPrFromElements } from '@docx4j/generated-objects-ts/builders/wml';
|
|
4
5
|
import { Docx4JException } from '../../opc/exceptions.mjs';
|
|
5
|
-
import { W_NS, linkParents, walk, typeNameOf, revisionKindOf } from './tree.mjs';
|
|
6
|
+
import { W_NS, linkParents, walk, typeNameOf, revisionKindOf, childrenOf, rowsOf, cellsOf } from './tree.mjs';
|
|
6
7
|
/**
|
|
7
8
|
* `w:id` on these is the one annotation id space (ECMA-376 17.13.5.4): revisions, bookmarks,
|
|
8
9
|
* comment range marks and permissions share it. `w:comment/@w:id` (docx4j `Comments.Comment`,
|
|
@@ -104,24 +105,33 @@ export function canonical(value) {
|
|
|
104
105
|
});
|
|
105
106
|
}
|
|
106
107
|
/**
|
|
107
|
-
* Makes sure the part's root lists
|
|
108
|
-
*
|
|
109
|
-
* pattern - the writer of the content adds its prefix
|
|
110
|
-
* decision (CR-002 section 29)
|
|
111
|
-
*
|
|
112
|
-
*
|
|
108
|
+
* Makes sure the part's root lists a prefix in `mc:Ignorable`, appending it when absent, so that
|
|
109
|
+
* content in that prefix's namespace may be written in it; the facade then declares the prefix.
|
|
110
|
+
* This is docx4j's pattern - the writer of the content adds its prefix
|
|
111
|
+
* (`Paginate.declareW14Ignorable`) - and Jason's decision (CR-002 section 29). Since objects 0.3.1
|
|
112
|
+
* (its CR-008) the marshaller does it for every writer as well; these calls stay as the writer's
|
|
113
|
+
* own statement. False when there is no unmarshalled root to put it on. Every part a
|
|
114
|
+
* `Body` is over (document, header, footer, notes, comments) has a root that binds `mc:Ignorable`.
|
|
115
|
+
*
|
|
116
|
+
* The prefix must be the facade's conventional one for its namespace (`w15`, `w16du`): an
|
|
117
|
+
* application that does not know the namespace (Word 2010 and `w15`) opens the part only when the
|
|
118
|
+
* prefix is ignorable, as Word's own files declare it (CR-002 section 40).
|
|
113
119
|
*/
|
|
114
|
-
function
|
|
120
|
+
export function declareIgnorable(part, prefix) {
|
|
115
121
|
var _a;
|
|
116
122
|
const contents = part === null || part === void 0 ? void 0 : part.contents;
|
|
117
123
|
if (!contents || typeof contents !== 'object')
|
|
118
124
|
return false;
|
|
119
125
|
const root = (contents.value && typeof contents.value === 'object' ? contents.value : contents);
|
|
120
|
-
const prefixes = ((_a = root.ignorable) !== null && _a !== void 0 ? _a : '').split(/\s+/).filter((
|
|
121
|
-
if (!prefixes.includes(
|
|
122
|
-
root.ignorable = [...prefixes,
|
|
126
|
+
const prefixes = ((_a = root.ignorable) !== null && _a !== void 0 ? _a : '').split(/\s+/).filter((p) => p.length > 0);
|
|
127
|
+
if (!prefixes.includes(prefix))
|
|
128
|
+
root.ignorable = [...prefixes, prefix].join(' ');
|
|
123
129
|
return true;
|
|
124
130
|
}
|
|
131
|
+
/** `w16du` in the part's `mc:Ignorable`, so that `w16du:dateUtc` may be written in it. */
|
|
132
|
+
function declareDateUtc(part) {
|
|
133
|
+
return declareIgnorable(part, 'w16du');
|
|
134
|
+
}
|
|
125
135
|
function element(localPart, value) {
|
|
126
136
|
return { name: { namespaceURI: W_NS, localPart }, value };
|
|
127
137
|
}
|
|
@@ -294,49 +304,166 @@ export class ChangeTracker {
|
|
|
294
304
|
}
|
|
295
305
|
}
|
|
296
306
|
/**
|
|
297
|
-
* A whole paragraph inserted: the mark is marked inserted and every run of it
|
|
298
|
-
* `w:ins
|
|
299
|
-
*
|
|
307
|
+
* A whole paragraph inserted: the mark is marked inserted (unless it already is) and every run of it
|
|
308
|
+
* goes into a `w:ins` - the runs in its hyperlinks, inline content controls, fields and smart tags as
|
|
309
|
+
* well (`wrapNewRuns`). Word, splitting a paragraph, marks the *first* paragraph's mark instead and
|
|
310
|
+
* leaves the new one the original mark; marking the new paragraph's own mark is the same document once
|
|
300
311
|
* accepted or rejected and keeps the edit to the element that was added (CR-002 section 13).
|
|
301
312
|
*/
|
|
302
313
|
export function trackInsertedParagraph(tracker, p) {
|
|
303
|
-
|
|
314
|
+
var _a, _b;
|
|
315
|
+
if (!((_b = (_a = p.pPr) === null || _a === void 0 ? void 0 : _a.rPr) === null || _b === void 0 ? void 0 : _b.ins))
|
|
316
|
+
tracker.markParagraphInserted(p);
|
|
304
317
|
wrapNewRuns(tracker, p);
|
|
305
318
|
}
|
|
306
|
-
/**
|
|
319
|
+
/**
|
|
320
|
+
* Every run of the paragraph not already in a revision, wrapped in one `w:ins` per group of touching
|
|
321
|
+
* runs - at the top level and inside the run-level holders, where Word's form decides (`test/README.md`
|
|
322
|
+
* check 30, CR-002 section 37 item 7): in a hyperlink or a simple field the `w:ins` goes inside, since
|
|
323
|
+
* neither can be in one (`CT_RunTrackChange` holds `EG_ContentRunContent` only); a smart tag goes
|
|
324
|
+
* *into* the `w:ins` with the runs beside it, its own runs plain; an inline content control or custom
|
|
325
|
+
* XML element keeps the `w:ins` inside and is itself marked inserted (`markHolderInserted`). Before,
|
|
326
|
+
* only the top level was wrapped, so that rejecting an inserted paragraph left a link's text behind
|
|
327
|
+
* (the editor's finding on 0.3.0, CR-002 section 37 item 4).
|
|
328
|
+
*/
|
|
307
329
|
export function wrapNewRuns(tracker, p) {
|
|
308
|
-
|
|
309
|
-
|
|
330
|
+
wrapInsertedRuns(tracker, p.content, p);
|
|
331
|
+
}
|
|
332
|
+
/** True for what goes into a `w:ins` beside the runs: a run, or a smart tag (Word's form, check 30). */
|
|
333
|
+
function wrapsAsRun(item) {
|
|
334
|
+
const tn = typeNameOf(item);
|
|
335
|
+
return (tn === 'org_docx4j_wml.R' || tn === 'org_docx4j_wml.CTSmartTagRun') && revisionKindOf(item) === undefined;
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* `wrapNewRuns` over run-level items that are about to go into a paragraph (`Paragraph.insertItemsAt`):
|
|
339
|
+
* the runs among them, and those in their holders, wrapped in `w:ins`; a content control or custom
|
|
340
|
+
* XML element among them marked inserted. `parent` is what the items' array belongs to.
|
|
341
|
+
*/
|
|
342
|
+
export function wrapInsertedRuns(tracker, items, parent) {
|
|
343
|
+
var _a, _b;
|
|
344
|
+
if (!items)
|
|
310
345
|
return;
|
|
311
|
-
for (let i = 0; i <
|
|
312
|
-
|
|
346
|
+
for (let i = 0; i < items.length; i++) {
|
|
347
|
+
const item = items[i];
|
|
348
|
+
if (revisionKindOf(item) !== undefined)
|
|
349
|
+
continue; // already a revision
|
|
350
|
+
if (wrapsAsRun(item)) {
|
|
351
|
+
let j = i;
|
|
352
|
+
while (j < items.length && wrapsAsRun(items[j]))
|
|
353
|
+
j++;
|
|
354
|
+
const runs = items.splice(i, j - i);
|
|
355
|
+
const ins = tracker.ins(runs);
|
|
356
|
+
items.splice(i, 0, ins);
|
|
357
|
+
linkParents(ins, parent);
|
|
358
|
+
continue;
|
|
359
|
+
}
|
|
360
|
+
const value = item.value;
|
|
361
|
+
if (typeof value !== 'object' || value === null)
|
|
362
|
+
continue;
|
|
363
|
+
const holder = (_a = value.sdtContent) !== null && _a !== void 0 ? _a : value;
|
|
364
|
+
if (HOLDER_TYPES.has((_b = typeNameOf(item)) !== null && _b !== void 0 ? _b : '')) {
|
|
365
|
+
const before = items.length;
|
|
366
|
+
markHolderInserted(tracker, item, items, () => wrapInsertedRuns(tracker, childrenOf(value), holder));
|
|
367
|
+
i += items.length - before;
|
|
313
368
|
continue;
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
j++;
|
|
317
|
-
const runs = content.splice(i, j - i);
|
|
318
|
-
const ins = tracker.ins(runs);
|
|
319
|
-
content.splice(i, 0, ins);
|
|
320
|
-
linkParents(ins, p);
|
|
369
|
+
}
|
|
370
|
+
wrapInsertedRuns(tracker, childrenOf(value), holder);
|
|
321
371
|
}
|
|
322
372
|
}
|
|
323
|
-
/** A whole table inserted: every row is marked inserted and
|
|
373
|
+
/** A whole table inserted: every row is marked inserted and everything in its cells is an insertion (`trackInsertedBlocks`). */
|
|
324
374
|
export function trackInsertedTable(tracker, tbl) {
|
|
325
|
-
var _a
|
|
326
|
-
for (const row of (
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
375
|
+
var _a;
|
|
376
|
+
for (const row of rowsOf(tbl)) {
|
|
377
|
+
const tr = row.element.value;
|
|
378
|
+
if (!((_a = tr.trPr) === null || _a === void 0 ? void 0 : _a.ins))
|
|
379
|
+
tracker.markRowInserted(tr);
|
|
380
|
+
for (const cell of cellsOf(tr))
|
|
381
|
+
trackInsertedBlocks(tracker, childrenOf(cell.element.value));
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* Block content inserted, whatever it holds: a paragraph (`trackInsertedParagraph`), a table
|
|
386
|
+
* (`trackInsertedTable`: rows inside row-level content controls, nested tables in its cells), and a
|
|
387
|
+
* block-level content control's or custom XML element's content, recursively, the element itself
|
|
388
|
+
* marked inserted (`markHolderInserted`; `owner` is the array holding `block`, which takes the
|
|
389
|
+
* markers). What `Body.insertElement` and `Table.addRows` track new content by (CR-002 section 37).
|
|
390
|
+
*/
|
|
391
|
+
export function trackInsertedBlock(tracker, block, owner) {
|
|
392
|
+
const tn = typeNameOf(block);
|
|
393
|
+
if (tn === 'org_docx4j_wml.P')
|
|
394
|
+
trackInsertedParagraph(tracker, block.value);
|
|
395
|
+
else if (tn === 'org_docx4j_wml.Tbl')
|
|
396
|
+
trackInsertedTable(tracker, block.value);
|
|
397
|
+
else if (typeof block.value !== 'object' || block.value === null)
|
|
398
|
+
return;
|
|
399
|
+
else if (HOLDER_TYPES.has(tn !== null && tn !== void 0 ? tn : ''))
|
|
400
|
+
markHolderInserted(tracker, block, owner, () => trackInsertedBlocks(tracker, childrenOf(block.value)));
|
|
401
|
+
else
|
|
402
|
+
trackInsertedBlocks(tracker, childrenOf(block.value));
|
|
403
|
+
}
|
|
404
|
+
/** `trackInsertedBlock` over every item of a list, which is their owner. */
|
|
405
|
+
export function trackInsertedBlocks(tracker, items) {
|
|
406
|
+
if (!items)
|
|
407
|
+
return;
|
|
408
|
+
for (let i = 0; i < items.length; i++) {
|
|
409
|
+
const before = items.length;
|
|
410
|
+
trackInsertedBlock(tracker, items[i], items);
|
|
411
|
+
i += items.length - before; // past the markers just put in
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* The elements Word marks inserted as a whole with `w:customXmlInsRangeStart` / `End`: a content
|
|
416
|
+
* control or a custom XML element (check 30 measured the control at run and block level; a smart tag
|
|
417
|
+
* goes into the `w:ins` instead, and a hyperlink or field has no form of its own). Row- and cell-level
|
|
418
|
+
* ones are listed for the reader: `trackInsertedTable` walks through them to the rows and cells, and
|
|
419
|
+
* does not mark them.
|
|
420
|
+
*/
|
|
421
|
+
export const HOLDER_TYPES = new Set([
|
|
422
|
+
'org_docx4j_wml.SdtRun', 'org_docx4j_wml.SdtBlock', 'org_docx4j_wml.CTSdtRow', 'org_docx4j_wml.CTSdtCell',
|
|
423
|
+
'org_docx4j_wml.CTCustomXmlRun', 'org_docx4j_wml.CTCustomXmlBlock', 'org_docx4j_wml.CTCustomXmlRow', 'org_docx4j_wml.CTCustomXmlCell',
|
|
424
|
+
]);
|
|
425
|
+
/** The list a holder's content is in (`w:sdtContent`'s for a control, its own for custom XML), created when absent. */
|
|
426
|
+
export function holderItemsOf(value) {
|
|
427
|
+
var _a, _b;
|
|
428
|
+
var _c;
|
|
429
|
+
const v = value;
|
|
430
|
+
if (v.sdtContent)
|
|
431
|
+
return ((_a = (_c = v.sdtContent).content) !== null && _a !== void 0 ? _a : (_c.content = []));
|
|
432
|
+
if ('element' in v)
|
|
433
|
+
return ((_b = v.content) !== null && _b !== void 0 ? _b : (v.content = [])); // w:customXml carries w:element
|
|
434
|
+
return undefined;
|
|
435
|
+
}
|
|
436
|
+
/**
|
|
437
|
+
* A content control or custom XML element inserted under tracking, as Word writes it (check 30): a
|
|
438
|
+
* `w:customXmlInsRangeStart` before the element with its `w:customXmlInsRangeEnd` first inside its
|
|
439
|
+
* content, and a second start last inside with its end after the element. Rejecting takes the element
|
|
440
|
+
* away and leaves its content where it stood (`TrackedChange`'s `holder` target); without the markers
|
|
441
|
+
* an element holding only an insertion reads as one that was there before and was typed into, which
|
|
442
|
+
* rejecting keeps (check 30, existing-control-typed). `inside` tracks the content between the two
|
|
443
|
+
* pairs, so that the ids run in document order as Word's do.
|
|
444
|
+
*/
|
|
445
|
+
export function markHolderInserted(tracker, holder, owner, inside) {
|
|
446
|
+
var _a;
|
|
447
|
+
const at = owner.indexOf(holder);
|
|
448
|
+
const items = holderItemsOf(holder.value);
|
|
449
|
+
if (at < 0 || !items) {
|
|
450
|
+
inside();
|
|
451
|
+
return;
|
|
339
452
|
}
|
|
453
|
+
const value = holder.value;
|
|
454
|
+
const first = tracker.markup();
|
|
455
|
+
const outerStart = el.customXmlInsRangeStart(createCTTrackChange(first));
|
|
456
|
+
const innerEnd = el.customXmlInsRangeEnd(createCTMarkup({ id: first.id }));
|
|
457
|
+
owner.splice(at, 0, outerStart);
|
|
458
|
+
items.unshift(innerEnd);
|
|
459
|
+
inside();
|
|
460
|
+
const second = tracker.markup();
|
|
461
|
+
const innerStart = el.customXmlInsRangeStart(createCTTrackChange(second));
|
|
462
|
+
const outerEnd = el.customXmlInsRangeEnd(createCTMarkup({ id: second.id }));
|
|
463
|
+
items.push(innerStart);
|
|
464
|
+
owner.splice(owner.indexOf(holder) + 1, 0, outerEnd);
|
|
465
|
+
linkParents([outerStart, outerEnd], value.PARENT);
|
|
466
|
+
linkParents([innerEnd, innerStart], (_a = value.sdtContent) !== null && _a !== void 0 ? _a : value);
|
|
340
467
|
}
|
|
341
468
|
/** The paragraph mark's run properties (`w:pPr/w:rPr`), created when absent. */
|
|
342
469
|
export function paraRPrOf(p) {
|
|
@@ -8,7 +8,9 @@ export interface CustomXmlPartLookup {
|
|
|
8
8
|
}
|
|
9
9
|
/**
|
|
10
10
|
* A subset of Office JS `Word.XmlMapping`: the content control's data binding. `isMapped` is
|
|
11
|
-
* whether the control has a `w:dataBinding
|
|
11
|
+
* whether the control has a `w:dataBinding` (or, on a rich text control or a repeating section,
|
|
12
|
+
* the `w15:dataBinding` Word writes there, which is what `setMapping` writes on one: CR-002
|
|
13
|
+
* section 40); `customXmlNode` evaluates the XPath now, so it needs
|
|
12
14
|
* the XPath engine to be warm (`await pkg.customXmlParts.load()` once, CR-002 section 3.6).
|
|
13
15
|
*/
|
|
14
16
|
export declare class XmlMapping {
|
|
@@ -33,13 +35,27 @@ export declare class XmlMapping {
|
|
|
33
35
|
* Binds the control to what the XPath selects, as Word's own mapping does: the part is the one
|
|
34
36
|
* given, else the one the control is already bound to, else the first custom XML part (the
|
|
35
37
|
* built-in property stores last) in which the XPath selects a node. Returns false and changes
|
|
36
|
-
* nothing when nothing is selected, which is what Word reports.
|
|
38
|
+
* nothing when nothing is selected, which is what Word reports. **The node's value then goes into
|
|
39
|
+
* the control**, whatever text it held: Office JS's `setMapping` on a plain text control holding
|
|
40
|
+
* "quick brown", on an empty one and on one already holding the value, and Word's XML Mapping
|
|
41
|
+
* Pane mapping an existing control, each showed the node's "Ann" and left the part unchanged
|
|
42
|
+
* (`test/README.md` check 34, 2026-10-03; CR-002 section 39). Before, only the binding was
|
|
43
|
+
* written and the values stayed apart until `applyBindings`.
|
|
37
44
|
*/
|
|
38
45
|
setMapping(xpath: string, prefixMappings?: string, part?: CustomXmlPart): boolean;
|
|
39
|
-
/** Binds to a node: its canonical XPath and the prefixes that path needs, as Word writes them. */
|
|
46
|
+
/** Binds to a node: its canonical XPath and the prefixes that path needs, as Word writes them; the node's value goes into the control (check 34). */
|
|
40
47
|
setMappingByNode(node: CustomXmlNode): boolean;
|
|
41
|
-
/** Removes the binding (`w:dataBinding`); the control keeps the content it shows. */
|
|
48
|
+
/** Removes the binding (`w:dataBinding` and `w15:dataBinding`); the control keeps the content it shows. */
|
|
42
49
|
delete(): void;
|
|
50
|
+
/**
|
|
51
|
+
* Whether the control's binding is `w15:dataBinding`, as Word 2013 and later write it: on a rich
|
|
52
|
+
* text control (one with `w:richText`, or with no kind element at all) and on a repeating
|
|
53
|
+
* section. `w:dataBinding` on a rich text control makes it a plain text control to Word 15 and
|
|
54
|
+
* Word 2010 alike - shown from its node on open, given `<w:text/>` on saving, an edit written
|
|
55
|
+
* back as text (`test/README.md` check 35, 1f; CR-002 section 40).
|
|
56
|
+
*/
|
|
57
|
+
get usesW15(): boolean;
|
|
58
|
+
/** Writes the binding in the element the control's kind takes, replacing one in the other namespace where it stood. */
|
|
43
59
|
private write;
|
|
44
60
|
/** The parts to try, the customer's data before Word's property stores. */
|
|
45
61
|
private candidates;
|
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import * as el from '@docx4j/generated-objects-ts/el/org_docx4j_wml';
|
|
2
|
+
import * as w15el from '@docx4j/generated-objects-ts/el/org_docx4j_w15';
|
|
2
3
|
import { sdtProperty } from '@docx4j/generated-objects-ts/builders/wml';
|
|
3
4
|
import { canonicalXPathOf } from './xpath.mjs';
|
|
5
|
+
import { applyBindingTo } from './bindings.mjs';
|
|
4
6
|
/** The Word 2012 (w15) namespace; not imported from `ContentControl`, which imports this module. */
|
|
5
7
|
const W15_NS = 'http://schemas.microsoft.com/office/word/2012/wordml';
|
|
8
|
+
const W_NS = 'http://schemas.openxmlformats.org/wordprocessingml/2006/main';
|
|
6
9
|
/**
|
|
7
10
|
* A subset of Office JS `Word.XmlMapping`: the content control's data binding. `isMapped` is
|
|
8
|
-
* whether the control has a `w:dataBinding
|
|
11
|
+
* whether the control has a `w:dataBinding` (or, on a rich text control or a repeating section,
|
|
12
|
+
* the `w15:dataBinding` Word writes there, which is what `setMapping` writes on one: CR-002
|
|
13
|
+
* section 40); `customXmlNode` evaluates the XPath now, so it needs
|
|
9
14
|
* the XPath engine to be warm (`await pkg.customXmlParts.load()` once, CR-002 section 3.6).
|
|
10
15
|
*/
|
|
11
16
|
export class XmlMapping {
|
|
@@ -58,7 +63,12 @@ export class XmlMapping {
|
|
|
58
63
|
* Binds the control to what the XPath selects, as Word's own mapping does: the part is the one
|
|
59
64
|
* given, else the one the control is already bound to, else the first custom XML part (the
|
|
60
65
|
* built-in property stores last) in which the XPath selects a node. Returns false and changes
|
|
61
|
-
* nothing when nothing is selected, which is what Word reports.
|
|
66
|
+
* nothing when nothing is selected, which is what Word reports. **The node's value then goes into
|
|
67
|
+
* the control**, whatever text it held: Office JS's `setMapping` on a plain text control holding
|
|
68
|
+
* "quick brown", on an empty one and on one already holding the value, and Word's XML Mapping
|
|
69
|
+
* Pane mapping an existing control, each showed the node's "Ann" and left the part unchanged
|
|
70
|
+
* (`test/README.md` check 34, 2026-10-03; CR-002 section 39). Before, only the binding was
|
|
71
|
+
* written and the values stayed apart until `applyBindings`.
|
|
62
72
|
*/
|
|
63
73
|
setMapping(xpath, prefixMappings, part) {
|
|
64
74
|
const candidates = part ? [part] : this.candidates();
|
|
@@ -67,25 +77,53 @@ export class XmlMapping {
|
|
|
67
77
|
if (!node)
|
|
68
78
|
continue;
|
|
69
79
|
this.write(xpath, prefixMappings !== null && prefixMappings !== void 0 ? prefixMappings : '', candidate.id);
|
|
80
|
+
applyBindingTo(this.contentControl);
|
|
70
81
|
return true;
|
|
71
82
|
}
|
|
72
83
|
return false;
|
|
73
84
|
}
|
|
74
|
-
/** Binds to a node: its canonical XPath and the prefixes that path needs, as Word writes them. */
|
|
85
|
+
/** Binds to a node: its canonical XPath and the prefixes that path needs, as Word writes them; the node's value goes into the control (check 34). */
|
|
75
86
|
setMappingByNode(node) {
|
|
76
87
|
const { xpath, prefixMappings } = canonicalXPathOf(node.node);
|
|
77
88
|
this.write(xpath, prefixMappings, node.ownerPart.id);
|
|
89
|
+
applyBindingTo(this.contentControl);
|
|
78
90
|
return true;
|
|
79
91
|
}
|
|
80
|
-
/** Removes the binding (`w:dataBinding`); the control keeps the content it shows. */
|
|
92
|
+
/** Removes the binding (`w:dataBinding` and `w15:dataBinding`); the control keeps the content it shows. */
|
|
81
93
|
delete() {
|
|
82
|
-
this.contentControl.removeProperty('dataBinding');
|
|
94
|
+
this.contentControl.removeProperty('dataBinding', W_NS);
|
|
95
|
+
this.contentControl.removeProperty('dataBinding', W15_NS);
|
|
83
96
|
}
|
|
97
|
+
/**
|
|
98
|
+
* Whether the control's binding is `w15:dataBinding`, as Word 2013 and later write it: on a rich
|
|
99
|
+
* text control (one with `w:richText`, or with no kind element at all) and on a repeating
|
|
100
|
+
* section. `w:dataBinding` on a rich text control makes it a plain text control to Word 15 and
|
|
101
|
+
* Word 2010 alike - shown from its node on open, given `<w:text/>` on saving, an edit written
|
|
102
|
+
* back as text (`test/README.md` check 35, 1f; CR-002 section 40).
|
|
103
|
+
*/
|
|
104
|
+
get usesW15() {
|
|
105
|
+
const type = this.contentControl.type;
|
|
106
|
+
return type === 'RichText' || type === 'RepeatingSection';
|
|
107
|
+
}
|
|
108
|
+
/** Writes the binding in the element the control's kind takes, replacing one in the other namespace where it stood. */
|
|
84
109
|
write(xpath, prefixMappings, storeItemID) {
|
|
110
|
+
var _a, _b;
|
|
85
111
|
const binding = { TYPE_NAME: 'org_docx4j_wml.CTDataBinding', xpath, storeItemID };
|
|
86
112
|
if (prefixMappings !== '')
|
|
87
113
|
binding.prefixMappings = prefixMappings;
|
|
88
|
-
this.
|
|
114
|
+
const w15 = this.usesW15;
|
|
115
|
+
const element = (w15 ? w15el.dataBinding(binding) : el.dataBinding(binding));
|
|
116
|
+
const items = (_a = this.contentControl.sdt.sdtPr) === null || _a === void 0 ? void 0 : _a.rPrOrAliasOrLock;
|
|
117
|
+
const other = w15 ? W_NS : W15_NS;
|
|
118
|
+
const at = (_b = items === null || items === void 0 ? void 0 : items.findIndex((i) => i.name.localPart === 'dataBinding' && i.name.namespaceURI === other)) !== null && _b !== void 0 ? _b : -1;
|
|
119
|
+
if (items && at >= 0) {
|
|
120
|
+
if (items.some((i) => i.name.localPart === 'dataBinding' && i.name.namespaceURI === element.name.namespaceURI))
|
|
121
|
+
items.splice(at, 1);
|
|
122
|
+
else
|
|
123
|
+
items[at] = element;
|
|
124
|
+
}
|
|
125
|
+
// putProperty also lists w15 in the part's mc:Ignorable when the element is a w15 one
|
|
126
|
+
this.contentControl.putProperty(element);
|
|
89
127
|
}
|
|
90
128
|
/** The parts to try, the customer's data before Word's property stores. */
|
|
91
129
|
candidates() {
|
|
@@ -13,6 +13,13 @@ export interface BindingResult {
|
|
|
13
13
|
/** Controls left alone: the part is missing, the XPath selects nothing, or the kind is not handled. */
|
|
14
14
|
skipped: number;
|
|
15
15
|
}
|
|
16
|
+
/**
|
|
17
|
+
* Whether a node's value is a Flat OPC package: what Word writes into the node of a mapped rich
|
|
18
|
+
* text control when its content is edited, a whole `pkg:package` as escaped text (`test/README.md`
|
|
19
|
+
* check 35). It is content, never a control's text - Word 2010 shows it as raw XML in a plain text
|
|
20
|
+
* control bound to the same node - so neither direction moves it as text (CR-002 section 40).
|
|
21
|
+
*/
|
|
22
|
+
export declare function holdsFlatOpc(value: string): boolean;
|
|
16
23
|
/** docx4j ValueInserterPlainTextImpl: the runs for a value, with the w:sdtPr run properties. */
|
|
17
24
|
export declare function runsForValue(value: string, rPr: wml.RPr | undefined, multiLine: boolean): Element[];
|
|
18
25
|
/**
|
|
@@ -24,8 +31,8 @@ export declare function runsForValue(value: string, rPr: wml.RPr | undefined, mu
|
|
|
24
31
|
export declare function formatDate(pattern: string, date: Date, locale?: string): string;
|
|
25
32
|
/**
|
|
26
33
|
* Pushes the custom XML value into one control (docx4j BindingHandler's per-sdt work). Returns
|
|
27
|
-
* false when nothing was written: no binding, no part, an XPath that selects nothing,
|
|
28
|
-
* this phase does not handle (pictures, explicit rich text).
|
|
34
|
+
* false when nothing was written: no binding, no part, an XPath that selects nothing, a kind
|
|
35
|
+
* this phase does not handle (pictures, explicit rich text), or a node holding a Flat OPC package.
|
|
29
36
|
*/
|
|
30
37
|
export declare function applyBindingTo(control: ContentControl): boolean;
|
|
31
38
|
/** `applyBindings` over a list of controls. */
|
|
@@ -4,6 +4,16 @@ import { sdtProperty } from '@docx4j/generated-objects-ts/builders/wml';
|
|
|
4
4
|
/** The style Word gives placeholder text, and the text docx4j's placeholder.xml carries. */
|
|
5
5
|
export const PLACEHOLDER_STYLE = 'PlaceholderText';
|
|
6
6
|
export const PLACEHOLDER_TEXT = 'Click here to enter text.';
|
|
7
|
+
/**
|
|
8
|
+
* Whether a node's value is a Flat OPC package: what Word writes into the node of a mapped rich
|
|
9
|
+
* text control when its content is edited, a whole `pkg:package` as escaped text (`test/README.md`
|
|
10
|
+
* check 35). It is content, never a control's text - Word 2010 shows it as raw XML in a plain text
|
|
11
|
+
* control bound to the same node - so neither direction moves it as text (CR-002 section 40).
|
|
12
|
+
*/
|
|
13
|
+
export function holdsFlatOpc(value) {
|
|
14
|
+
return /^\s*(<\?[^>]*\?>\s*)*<([\w.-]+:)?package[\s>]/.test(value)
|
|
15
|
+
&& value.includes('http://schemas.microsoft.com/office/2006/xmlPackage');
|
|
16
|
+
}
|
|
7
17
|
function isTrue(value) {
|
|
8
18
|
const v = value.trim().toLowerCase();
|
|
9
19
|
return v === 'true' || v === '1' || v === 'on' || v === 'yes';
|
|
@@ -103,8 +113,8 @@ function dateOf(value) {
|
|
|
103
113
|
}
|
|
104
114
|
/**
|
|
105
115
|
* Pushes the custom XML value into one control (docx4j BindingHandler's per-sdt work). Returns
|
|
106
|
-
* false when nothing was written: no binding, no part, an XPath that selects nothing,
|
|
107
|
-
* this phase does not handle (pictures, explicit rich text).
|
|
116
|
+
* false when nothing was written: no binding, no part, an XPath that selects nothing, a kind
|
|
117
|
+
* this phase does not handle (pictures, explicit rich text), or a node holding a Flat OPC package.
|
|
108
118
|
*/
|
|
109
119
|
export function applyBindingTo(control) {
|
|
110
120
|
var _a;
|
|
@@ -123,6 +133,9 @@ export function applyBindingTo(control) {
|
|
|
123
133
|
return false;
|
|
124
134
|
if (isContainer(control))
|
|
125
135
|
return false;
|
|
136
|
+
// A Flat OPC package is a rich text control's content, not text: no control is filled with it.
|
|
137
|
+
if (holdsFlatOpc(value))
|
|
138
|
+
return false;
|
|
126
139
|
if (control.type === 'CheckBox') {
|
|
127
140
|
const checkbox = control.checkboxContentControl;
|
|
128
141
|
if (!checkbox)
|
|
@@ -190,6 +203,9 @@ export function updateFromControl(control) {
|
|
|
190
203
|
// A control showing its placeholder holds no value.
|
|
191
204
|
if (control.isShowingPlaceholder)
|
|
192
205
|
return false;
|
|
206
|
+
// The node holds a rich text control's content as Flat OPC: text is not written over it.
|
|
207
|
+
if (holdsFlatOpc(node.text))
|
|
208
|
+
return false;
|
|
193
209
|
let value = control.text;
|
|
194
210
|
if (control.type === 'CheckBox') {
|
|
195
211
|
const checkbox = control.checkboxContentControl;
|
|
@@ -3,5 +3,5 @@ export { CustomXmlPart, CustomXmlNode, CustomXmlPrefixMappingCollection, type Cu
|
|
|
3
3
|
export { CustomXmlPartCollection, type CustomXmlHost } from './CustomXmlPartCollection.mjs';
|
|
4
4
|
export { XmlMapping, type CustomXmlPartLookup } from './XmlMapping.mjs';
|
|
5
5
|
export { CheckboxContentControl, DatePickerContentControl, ListContentControl, ContentControlListItem, PictureContentControl, RepeatingSectionContentControl, GroupContentControl, checkboxRun, CHECKED_SYMBOL, UNCHECKED_SYMBOL, CHECKBOX_FONT, } from './kinds.mjs';
|
|
6
|
-
export { applyBindingsTo, applyBindingTo, updateFromControls, updateFromControl, runsForValue, formatDate, PLACEHOLDER_STYLE, PLACEHOLDER_TEXT, type BindingResult } from './bindings.mjs';
|
|
6
|
+
export { applyBindingsTo, applyBindingTo, updateFromControls, updateFromControl, runsForValue, formatDate, holdsFlatOpc, PLACEHOLDER_STYLE, PLACEHOLDER_TEXT, type BindingResult } from './bindings.mjs';
|
|
7
7
|
export { controlIdScope, sdtKindFor, isRunLevel } from './insert.mjs';
|
|
@@ -5,5 +5,5 @@ export { CustomXmlPart, CustomXmlNode, CustomXmlPrefixMappingCollection } from '
|
|
|
5
5
|
export { CustomXmlPartCollection } from './CustomXmlPartCollection.mjs';
|
|
6
6
|
export { XmlMapping } from './XmlMapping.mjs';
|
|
7
7
|
export { CheckboxContentControl, DatePickerContentControl, ListContentControl, ContentControlListItem, PictureContentControl, RepeatingSectionContentControl, GroupContentControl, checkboxRun, CHECKED_SYMBOL, UNCHECKED_SYMBOL, CHECKBOX_FONT, } from './kinds.mjs';
|
|
8
|
-
export { applyBindingsTo, applyBindingTo, updateFromControls, updateFromControl, runsForValue, formatDate, PLACEHOLDER_STYLE, PLACEHOLDER_TEXT } from './bindings.mjs';
|
|
8
|
+
export { applyBindingsTo, applyBindingTo, updateFromControls, updateFromControl, runsForValue, formatDate, holdsFlatOpc, PLACEHOLDER_STYLE, PLACEHOLDER_TEXT } from './bindings.mjs';
|
|
9
9
|
export { controlIdScope, sdtKindFor, isRunLevel } from './insert.mjs';
|
|
@@ -16,5 +16,15 @@ export declare function controlIdScope(body: {
|
|
|
16
16
|
part?: XmlPart<unknown> | undefined;
|
|
17
17
|
container: object;
|
|
18
18
|
}): object;
|
|
19
|
+
/**
|
|
20
|
+
* Lists `w15` in the `mc:Ignorable` of the body's part when the new control's `w:sdtPr` holds a
|
|
21
|
+
* `w15` element (a repeating section or its item): Word 2010 knows no `w15` and opens a part with
|
|
22
|
+
* one only when the prefix is ignorable (CR-002 section 40, `test/README.md` check 35).
|
|
23
|
+
*/
|
|
24
|
+
export declare function declareSdtExtensions(body: {
|
|
25
|
+
part?: XmlPart<unknown> | undefined;
|
|
26
|
+
}, sdt: {
|
|
27
|
+
value?: unknown;
|
|
28
|
+
}): void;
|
|
19
29
|
/** True for a run-level item (what a Range wraps). */
|
|
20
30
|
export declare function isRunLevel(element: Element): boolean;
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { typeNameOf } from '../content/tree.mjs';
|
|
2
|
+
import { declareIgnorable } from '../content/tracking.mjs';
|
|
3
|
+
const W15_NS = 'http://schemas.microsoft.com/office/word/2012/wordml';
|
|
2
4
|
/**
|
|
3
5
|
* The kind the builders' `sdt` takes: Office JS's `Unknown` has no kind element, as `RichText`
|
|
4
6
|
* has none (Word reports an untyped control as rich text).
|
|
@@ -15,6 +17,17 @@ export function controlIdScope(body) {
|
|
|
15
17
|
const part = body.part;
|
|
16
18
|
return (part === null || part === void 0 ? void 0 : part.isUnmarshalled) ? part.contents : body.container;
|
|
17
19
|
}
|
|
20
|
+
/**
|
|
21
|
+
* Lists `w15` in the `mc:Ignorable` of the body's part when the new control's `w:sdtPr` holds a
|
|
22
|
+
* `w15` element (a repeating section or its item): Word 2010 knows no `w15` and opens a part with
|
|
23
|
+
* one only when the prefix is ignorable (CR-002 section 40, `test/README.md` check 35).
|
|
24
|
+
*/
|
|
25
|
+
export function declareSdtExtensions(body, sdt) {
|
|
26
|
+
var _a, _b;
|
|
27
|
+
const items = (_b = (_a = sdt.value) === null || _a === void 0 ? void 0 : _a.sdtPr) === null || _b === void 0 ? void 0 : _b.rPrOrAliasOrLock;
|
|
28
|
+
if (items === null || items === void 0 ? void 0 : items.some((item) => { var _a; return ((_a = item.name) === null || _a === void 0 ? void 0 : _a.namespaceURI) === W15_NS; }))
|
|
29
|
+
declareIgnorable(body.part, 'w15');
|
|
30
|
+
}
|
|
18
31
|
/** True for a run-level item (what a Range wraps). */
|
|
19
32
|
export function isRunLevel(element) {
|
|
20
33
|
const tn = typeNameOf(element);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type * as wml from '@docx4j/generated-objects-ts/modules/org_docx4j_wml';
|
|
2
2
|
import type { PropertyResolver } from '../properties/PropertyResolver.mjs';
|
|
3
|
+
import type { CellContext } from '../properties/table.mjs';
|
|
3
4
|
import { type ThemeFontLang, type ThemeSource } from './ThemeFonts.mjs';
|
|
4
5
|
import { type DefaultTheme } from './defaultTheme.mjs';
|
|
5
6
|
/** Word's built-in default where the document defaults name no font at all (docx4j CR-016
|
|
@@ -41,6 +42,11 @@ export interface RunFontSelectorSource {
|
|
|
41
42
|
export interface SpansOptions {
|
|
42
43
|
/** True where the caller has resolved the run's effective properties already. */
|
|
43
44
|
rPrIsEffective?: boolean;
|
|
45
|
+
/**
|
|
46
|
+
* The paragraph's table context (`PropertyResolver.cellContextOf(p)`, or a tracker's), so a run
|
|
47
|
+
* in a table cell is resolved with its table style's formatting (CR-007; docx4j CR-030 phase 5).
|
|
48
|
+
*/
|
|
49
|
+
cellContext?: CellContext | undefined;
|
|
44
50
|
}
|
|
45
51
|
/** `w:cs` and `w:rtl` are `ST_OnOff`: present without `w:val` means true, `w:val="0"` false. */
|
|
46
52
|
export declare function isOn(value: wml.BooleanDefaultTrue | undefined): boolean;
|
|
@@ -149,8 +155,9 @@ export declare class RunFontSelector {
|
|
|
149
155
|
* at least.
|
|
150
156
|
*
|
|
151
157
|
* @param rPrIsEffective true where the caller has resolved the run's properties already
|
|
158
|
+
* @param cellContext the paragraph's table context, where it is in a table cell (CR-007)
|
|
152
159
|
*/
|
|
153
|
-
documentFontFor(pPr: wml.PPr | undefined, rPr: wml.RPr | undefined, codePoint: number, rPrIsEffective?: boolean): string;
|
|
160
|
+
documentFontFor(pPr: wml.PPr | undefined, rPr: wml.RPr | undefined, codePoint: number, rPrIsEffective?: boolean, cellContext?: CellContext | undefined): string;
|
|
154
161
|
/**
|
|
155
162
|
* The run's text folded into maximal stretches of one document font.
|
|
156
163
|
*
|
|
@@ -173,10 +173,10 @@ export class RunFontSelector {
|
|
|
173
173
|
return this.defaultFont;
|
|
174
174
|
}
|
|
175
175
|
/** The run's effective properties, never undefined. */
|
|
176
|
-
effectiveRPr(pPr, rPr, rPrIsEffective) {
|
|
176
|
+
effectiveRPr(pPr, rPr, rPrIsEffective, cellContext) {
|
|
177
177
|
var _a;
|
|
178
178
|
if (!rPrIsEffective && this.source.resolver !== undefined) {
|
|
179
|
-
return (_a = this.source.resolver.getEffectiveRPr(rPr, pPr)) !== null && _a !== void 0 ? _a : {};
|
|
179
|
+
return (_a = this.source.resolver.getEffectiveRPr(rPr, pPr, cellContext)) !== null && _a !== void 0 ? _a : {};
|
|
180
180
|
}
|
|
181
181
|
return rPr !== null && rPr !== void 0 ? rPr : {};
|
|
182
182
|
}
|
|
@@ -259,10 +259,11 @@ export class RunFontSelector {
|
|
|
259
259
|
* at least.
|
|
260
260
|
*
|
|
261
261
|
* @param rPrIsEffective true where the caller has resolved the run's properties already
|
|
262
|
+
* @param cellContext the paragraph's table context, where it is in a table cell (CR-007)
|
|
262
263
|
*/
|
|
263
|
-
documentFontFor(pPr, rPr, codePoint, rPrIsEffective = false) {
|
|
264
|
+
documentFontFor(pPr, rPr, codePoint, rPrIsEffective = false, cellContext) {
|
|
264
265
|
var _a;
|
|
265
|
-
const effective = this.effectiveRPr(pPr, rPr, rPrIsEffective);
|
|
266
|
+
const effective = this.effectiveRPr(pPr, rPr, rPrIsEffective, cellContext);
|
|
266
267
|
return (_a = this.fontOf(this.slotsOf(pPr, effective), codePoint)) !== null && _a !== void 0 ? _a : this.defaultFont;
|
|
267
268
|
}
|
|
268
269
|
/**
|
|
@@ -280,7 +281,7 @@ export class RunFontSelector {
|
|
|
280
281
|
const spans = [];
|
|
281
282
|
if (text === undefined || text === null || text.length === 0)
|
|
282
283
|
return spans;
|
|
283
|
-
const effective = this.effectiveRPr(pPr, rPr, options.rPrIsEffective === true);
|
|
284
|
+
const effective = this.effectiveRPr(pPr, rPr, options.rPrIsEffective === true, options.cellContext);
|
|
284
285
|
const bold = isOn(effective.b);
|
|
285
286
|
const italic = isOn(effective.i);
|
|
286
287
|
const cs = isOn(effective.cs);
|