@docx4j/core-ts 0.3.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/README.md +3 -1
  2. package/dist/html/convert.d.mts +116 -0
  3. package/dist/html/convert.mjs +541 -0
  4. package/dist/html/elements.d.mts +19 -0
  5. package/dist/html/elements.mjs +182 -0
  6. package/dist/html/index.d.mts +2 -0
  7. package/dist/html/index.mjs +4 -0
  8. package/dist/model/content/Body.mjs +9 -1
  9. package/dist/model/content/ContentControl.d.mts +5 -1
  10. package/dist/model/content/ContentControl.mjs +18 -5
  11. package/dist/model/content/InlinePicture.d.mts +18 -3
  12. package/dist/model/content/InlinePicture.mjs +44 -7
  13. package/dist/model/content/Paragraph.d.mts +11 -11
  14. package/dist/model/content/Paragraph.mjs +30 -43
  15. package/dist/model/content/Range.mjs +3 -2
  16. package/dist/model/content/Table.d.mts +38 -0
  17. package/dist/model/content/Table.mjs +80 -2
  18. package/dist/model/content/index.d.mts +2 -1
  19. package/dist/model/content/index.mjs +2 -1
  20. package/dist/model/content/stylesFacade.d.mts +4 -2
  21. package/dist/model/content/stylesFacade.mjs +23 -6
  22. package/dist/model/content/tableColumns.d.mts +48 -0
  23. package/dist/model/content/tableColumns.mjs +484 -0
  24. package/dist/model/content/tracking.d.mts +14 -0
  25. package/dist/model/content/tracking.mjs +19 -10
  26. package/dist/model/customxml/CustomXmlPartCollection.d.mts +6 -1
  27. package/dist/model/customxml/CustomXmlPartCollection.mjs +19 -3
  28. package/dist/model/customxml/XmlMapping.d.mts +20 -4
  29. package/dist/model/customxml/XmlMapping.mjs +44 -6
  30. package/dist/model/customxml/bindings.d.mts +13 -3
  31. package/dist/model/customxml/bindings.mjs +38 -7
  32. package/dist/model/customxml/index.d.mts +4 -1
  33. package/dist/model/customxml/index.mjs +4 -1
  34. package/dist/model/customxml/insert.d.mts +10 -0
  35. package/dist/model/customxml/insert.mjs +13 -0
  36. package/dist/model/customxml/opendope.d.mts +32 -0
  37. package/dist/model/customxml/opendope.mjs +56 -0
  38. package/dist/model/customxml/pictures.d.mts +54 -0
  39. package/dist/model/customxml/pictures.mjs +186 -0
  40. package/dist/model/customxml/xhtml.d.mts +42 -0
  41. package/dist/model/customxml/xhtml.mjs +162 -0
  42. package/dist/model/fonts/RunFontSelector.d.mts +8 -1
  43. package/dist/model/fonts/RunFontSelector.mjs +6 -5
  44. package/dist/model/properties/PropertyResolver.d.mts +110 -10
  45. package/dist/model/properties/PropertyResolver.mjs +300 -31
  46. package/dist/model/properties/index.d.mts +3 -0
  47. package/dist/model/properties/index.mjs +2 -0
  48. package/dist/model/properties/table.d.mts +155 -0
  49. package/dist/model/properties/table.mjs +315 -0
  50. package/dist/model/properties/tableStyleConditions.d.mts +101 -0
  51. package/dist/model/properties/tableStyleConditions.mjs +296 -0
  52. package/dist/packages/WordprocessingMLPackage.mjs +1 -0
  53. package/dist/parts/wml/knownStyles.d.mts +1 -0
  54. package/dist/parts/wml/knownStyles.mjs +2233 -0
  55. package/dist/parts/wml/placeholderStyle.d.mts +3 -0
  56. package/dist/parts/wml/placeholderStyle.mjs +13 -0
  57. package/package.json +7 -2
@@ -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`; `customXmlNode` evaluates the XPath now, so it needs
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`; `customXmlNode` evaluates the XPath now, so it needs
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.contentControl.putProperty(el.dataBinding(binding));
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() {
@@ -12,7 +12,16 @@ export interface BindingResult {
12
12
  updated: number;
13
13
  /** Controls left alone: the part is missing, the XPath selects nothing, or the kind is not handled. */
14
14
  skipped: number;
15
+ /** What the XHTML and picture passes left or dropped, one line each (CR-005 sections 8.2 and 9; REQ-076). */
16
+ notes?: string[];
15
17
  }
18
+ /**
19
+ * Whether a node's value is a Flat OPC package: what Word writes into the node of a mapped rich
20
+ * text control when its content is edited, a whole `pkg:package` as escaped text (`test/README.md`
21
+ * check 35). It is content, never a control's text - Word 2010 shows it as raw XML in a plain text
22
+ * control bound to the same node - so neither direction moves it as text (CR-002 section 40).
23
+ */
24
+ export declare function holdsFlatOpc(value: string): boolean;
16
25
  /** docx4j ValueInserterPlainTextImpl: the runs for a value, with the w:sdtPr run properties. */
17
26
  export declare function runsForValue(value: string, rPr: wml.RPr | undefined, multiLine: boolean): Element[];
18
27
  /**
@@ -24,10 +33,11 @@ export declare function runsForValue(value: string, rPr: wml.RPr | undefined, mu
24
33
  export declare function formatDate(pattern: string, date: Date, locale?: string): string;
25
34
  /**
26
35
  * 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, or a kind
28
- * this phase does not handle (pictures, explicit rich text).
36
+ * false when nothing was written: no binding, no part, an XPath that selects nothing, a kind
37
+ * this phase does not handle (explicit rich text), a node holding a Flat OPC package, or a
38
+ * picture control whose node holds no image (`notes` says which; CR-005 section 9).
29
39
  */
30
- export declare function applyBindingTo(control: ContentControl): boolean;
40
+ export declare function applyBindingTo(control: ContentControl, notes?: string[]): boolean;
31
41
  /** `applyBindings` over a list of controls. */
32
42
  export declare function applyBindingsTo(controls: ContentControl[]): BindingResult;
33
43
  /**
@@ -1,9 +1,20 @@
1
1
  import { deepCopy } from '@docx4j/generated-objects-ts';
2
2
  import * as el from '@docx4j/generated-objects-ts/el/org_docx4j_wml';
3
3
  import { sdtProperty } from '@docx4j/generated-objects-ts/builders/wml';
4
+ import { bindPicture } from './pictures.mjs';
4
5
  /** The style Word gives placeholder text, and the text docx4j's placeholder.xml carries. */
5
6
  export const PLACEHOLDER_STYLE = 'PlaceholderText';
6
7
  export const PLACEHOLDER_TEXT = 'Click here to enter text.';
8
+ /**
9
+ * Whether a node's value is a Flat OPC package: what Word writes into the node of a mapped rich
10
+ * text control when its content is edited, a whole `pkg:package` as escaped text (`test/README.md`
11
+ * check 35). It is content, never a control's text - Word 2010 shows it as raw XML in a plain text
12
+ * control bound to the same node - so neither direction moves it as text (CR-002 section 40).
13
+ */
14
+ export function holdsFlatOpc(value) {
15
+ return /^\s*(<\?[^>]*\?>\s*)*<([\w.-]+:)?package[\s>]/.test(value)
16
+ && value.includes('http://schemas.microsoft.com/office/2006/xmlPackage');
17
+ }
7
18
  function isTrue(value) {
8
19
  const v = value.trim().toLowerCase();
9
20
  return v === 'true' || v === '1' || v === 'on' || v === 'yes';
@@ -103,10 +114,11 @@ function dateOf(value) {
103
114
  }
104
115
  /**
105
116
  * 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, or a kind
107
- * this phase does not handle (pictures, explicit rich text).
117
+ * false when nothing was written: no binding, no part, an XPath that selects nothing, a kind
118
+ * this phase does not handle (explicit rich text), a node holding a Flat OPC package, or a
119
+ * picture control whose node holds no image (`notes` says which; CR-005 section 9).
108
120
  */
109
- export function applyBindingTo(control) {
121
+ export function applyBindingTo(control, notes) {
110
122
  var _a;
111
123
  const mapping = control.xmlMapping;
112
124
  if (!mapping.isMapped)
@@ -115,14 +127,22 @@ export function applyBindingTo(control) {
115
127
  if (!node)
116
128
  return false;
117
129
  const value = node.text.trim();
118
- // A picture binding carries base64 image data; docx4j replaces the a:blip embed. Deferred (section 12).
119
- if (control.type === 'Picture')
120
- return false;
130
+ // A picture control's node holds base64 image data: the picture's a:blip is pointed at a new
131
+ // image part and the drawing kept, as docx4j's bind.xslt mode picture3 does (CR-005 section 9).
132
+ if (control.type === 'Picture') {
133
+ const outcome = bindPicture(control, value, { kind: 'keep' }, control.title || control.tag || `picture control ${control.id}`);
134
+ if (outcome.note)
135
+ notes === null || notes === void 0 ? void 0 : notes.push(outcome.note);
136
+ return outcome.done;
137
+ }
121
138
  // Explicit rich text is bound from flat OPC or XHTML in docx4j; deferred, as it is there for this route.
122
139
  if (sdtProperty(control.sdt.sdtPr, 'richText') !== undefined)
123
140
  return false;
124
141
  if (isContainer(control))
125
142
  return false;
143
+ // A Flat OPC package is a rich text control's content, not text: no control is filled with it.
144
+ if (holdsFlatOpc(value))
145
+ return false;
126
146
  if (control.type === 'CheckBox') {
127
147
  const checkbox = control.checkboxContentControl;
128
148
  if (!checkbox)
@@ -156,15 +176,18 @@ export function applyBindingTo(control) {
156
176
  /** `applyBindings` over a list of controls. */
157
177
  export function applyBindingsTo(controls) {
158
178
  const result = { bound: 0, updated: 0, skipped: 0 };
179
+ const notes = [];
159
180
  for (const control of controls) {
160
181
  if (!control.xmlMapping.isMapped)
161
182
  continue;
162
183
  result.bound++;
163
- if (applyBindingTo(control))
184
+ if (applyBindingTo(control, notes))
164
185
  result.updated++;
165
186
  else
166
187
  result.skipped++;
167
188
  }
189
+ if (notes.length > 0)
190
+ result.notes = notes;
168
191
  return result;
169
192
  }
170
193
  /**
@@ -181,6 +204,8 @@ export function updateFromControl(control) {
181
204
  const node = mapping.customXmlNode;
182
205
  if (!node)
183
206
  return false;
207
+ // A picture control is the asynchronous pass's (updateFromPictureControl, CR-005 section 9): its
208
+ // image's bytes go back to the node as base64, as Word 15 writes them (check 38).
184
209
  if (control.type === 'Picture')
185
210
  return false;
186
211
  if (sdtProperty(control.sdt.sdtPr, 'richText') !== undefined)
@@ -190,6 +215,9 @@ export function updateFromControl(control) {
190
215
  // A control showing its placeholder holds no value.
191
216
  if (control.isShowingPlaceholder)
192
217
  return false;
218
+ // The node holds a rich text control's content as Flat OPC: text is not written over it.
219
+ if (holdsFlatOpc(node.text))
220
+ return false;
193
221
  let value = control.text;
194
222
  if (control.type === 'CheckBox') {
195
223
  const checkbox = control.checkboxContentControl;
@@ -220,6 +248,9 @@ export function updateFromControls(controls) {
220
248
  for (const control of controls) {
221
249
  if (!control.xmlMapping.isMapped)
222
250
  continue;
251
+ // counted by the picture pass, which reads the image part (CR-005 section 9)
252
+ if (control.type === 'Picture')
253
+ continue;
223
254
  result.bound++;
224
255
  if (updateFromControl(control))
225
256
  result.updated++;
@@ -3,5 +3,8 @@ 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';
8
+ export { tagParamsOf, xpathsEntriesOf, selectEntryNode, OPENDOPE_XPATHS_NS, XHTML_CONTENT_TYPE, PICTURE_HANDLER, type XPathsEntry } from './opendope.mjs';
9
+ export { applyXhtmlBindingTo, applyXhtmlBindingsTo, isXhtmlBound, type XhtmlHost, type XhtmlBindingOptions } from './xhtml.mjs';
10
+ export { applyPictureHandlerTo, applyPictureHandlersTo, isPictureHandlerBound, bindPicture, pictureWidthOf, updateFromPictureControl, updateFromPictureControls, type PictureWidth, type PictureOutcome } from './pictures.mjs';
@@ -5,5 +5,8 @@ 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';
10
+ export { tagParamsOf, xpathsEntriesOf, selectEntryNode, OPENDOPE_XPATHS_NS, XHTML_CONTENT_TYPE, PICTURE_HANDLER } from './opendope.mjs';
11
+ export { applyXhtmlBindingTo, applyXhtmlBindingsTo, isXhtmlBound } from './xhtml.mjs';
12
+ export { applyPictureHandlerTo, applyPictureHandlersTo, isPictureHandlerBound, bindPicture, pictureWidthOf, updateFromPictureControl, updateFromPictureControls } from './pictures.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);
@@ -0,0 +1,32 @@
1
+ import type { CustomXmlPartLookup } from './XmlMapping.mjs';
2
+ import type { CustomXmlNode } from './CustomXmlPart.mjs';
3
+ export declare const OPENDOPE_XPATHS_NS = "http://opendope.org/xpaths";
4
+ export declare const XHTML_CONTENT_TYPE = "application/xhtml+xml";
5
+ /** The tag's `od:Handler` value for a picture (the specification's section 9.3; CR-005 section 9). */
6
+ export declare const PICTURE_HANDLER = "picture";
7
+ /** The tag's parameters, `key=value` pairs separated by `&`, in order; a value is as written (the content type keeps its `/` and `+`). */
8
+ export declare function tagParamsOf(tag: string): Map<string, string>;
9
+ /**
10
+ * An XPaths part entry: `<xpath id="x1"><dataBinding storeItemID xpath prefixMappings/></xpath>`.
11
+ * The attributes are unqualified (`xpaths.xsd`: `attributeFormDefault="unqualified"`; docx4j's
12
+ * `org.opendope.xpaths.Xpaths` and its invoice.docx write them so); a `w:`-qualified form, which
13
+ * this package's own check 37 and 38 files were made with, is read as well.
14
+ */
15
+ export interface XPathsEntry {
16
+ id: string;
17
+ storeItemID: string;
18
+ xpath: string;
19
+ prefixMappings: string;
20
+ }
21
+ /** The XPaths part's entries by id, from the first custom XML part in the OpenDoPE XPaths namespace; empty without one. */
22
+ export declare function xpathsEntriesOf(parts: CustomXmlPartLookup): Map<string, XPathsEntry>;
23
+ /**
24
+ * The node an `od:xpath` entry selects: the entry's XPath over the part its `storeItemID` names
25
+ * (the id compared without braces or case, as `getItem` does). What a tag-bound control's pass
26
+ * starts from; the note says why there is no node.
27
+ */
28
+ export declare function selectEntryNode(entryId: string, parts: CustomXmlPartLookup): {
29
+ node: CustomXmlNode;
30
+ } | {
31
+ note: string;
32
+ };
@@ -0,0 +1,56 @@
1
+ export const OPENDOPE_XPATHS_NS = 'http://opendope.org/xpaths';
2
+ export const XHTML_CONTENT_TYPE = 'application/xhtml+xml';
3
+ /** The tag's `od:Handler` value for a picture (the specification's section 9.3; CR-005 section 9). */
4
+ export const PICTURE_HANDLER = 'picture';
5
+ /** The tag's parameters, `key=value` pairs separated by `&`, in order; a value is as written (the content type keeps its `/` and `+`). */
6
+ export function tagParamsOf(tag) {
7
+ const out = new Map();
8
+ for (const pair of tag.split('&')) {
9
+ const eq = pair.indexOf('=');
10
+ if (eq <= 0)
11
+ continue;
12
+ const key = pair.slice(0, eq).trim();
13
+ if (!out.has(key))
14
+ out.set(key, pair.slice(eq + 1).trim());
15
+ }
16
+ return out;
17
+ }
18
+ /** The XPaths part's entries by id, from the first custom XML part in the OpenDoPE XPaths namespace; empty without one. */
19
+ export function xpathsEntriesOf(parts) {
20
+ const out = new Map();
21
+ const part = parts.items.find((p) => p.namespaceUri === OPENDOPE_XPATHS_NS);
22
+ if (!part)
23
+ return out;
24
+ const root = part.document.documentElement;
25
+ if (!root)
26
+ return out;
27
+ const entries = root.getElementsByTagNameNS(OPENDOPE_XPATHS_NS, 'xpath');
28
+ for (let i = 0; i < entries.length; i++) {
29
+ const entry = entries[i];
30
+ const id = entry.getAttribute('id');
31
+ const binding = entry.getElementsByTagNameNS(OPENDOPE_XPATHS_NS, 'dataBinding')[0];
32
+ if (!id || !binding)
33
+ continue;
34
+ const read = (name) => binding.getAttribute(name) || binding.getAttributeNS('http://schemas.openxmlformats.org/wordprocessingml/2006/main', name) || binding.getAttribute(`w:${name}`) || '';
35
+ out.set(id, { id, storeItemID: read('storeItemID'), xpath: read('xpath'), prefixMappings: read('prefixMappings') });
36
+ }
37
+ return out;
38
+ }
39
+ /**
40
+ * The node an `od:xpath` entry selects: the entry's XPath over the part its `storeItemID` names
41
+ * (the id compared without braces or case, as `getItem` does). What a tag-bound control's pass
42
+ * starts from; the note says why there is no node.
43
+ */
44
+ export function selectEntryNode(entryId, parts) {
45
+ var _a;
46
+ const entry = xpathsEntriesOf(parts).get(entryId);
47
+ if (!entry)
48
+ return { note: `od:xpath=${entryId}: no such entry in the XPaths part` };
49
+ const part = (_a = parts.getItem(entry.storeItemID)) !== null && _a !== void 0 ? _a : parts.items.find((p) => p.id.replace(/[{}]/g, '').toLowerCase() === entry.storeItemID.replace(/[{}]/g, '').toLowerCase());
50
+ if (!part)
51
+ return { note: `od:xpath=${entryId}: no custom XML part ${entry.storeItemID}` };
52
+ const node = part.selectSingleNode(entry.xpath, entry.prefixMappings);
53
+ if (!node)
54
+ return { note: `od:xpath=${entryId}: ${entry.xpath} selects nothing` };
55
+ return { node };
56
+ }
@@ -0,0 +1,54 @@
1
+ import type { ContentControl } from '../content/ContentControl.mjs';
2
+ import type { CustomXmlPartLookup } from './XmlMapping.mjs';
3
+ import type { BindingResult } from './bindings.mjs';
4
+ /**
5
+ * What the tag's `width` says: absent keeps the control's drawing and replaces its image; `auto`
6
+ * a new inline picture at its natural size, scaled down to the text width; `N` the same, scaled
7
+ * down to N twips when that is narrower than the text width (a value that is neither is `auto`,
8
+ * which is what docx4j does with one it cannot parse).
9
+ */
10
+ export type PictureWidth = {
11
+ kind: 'keep';
12
+ } | {
13
+ kind: 'auto';
14
+ } | {
15
+ kind: 'twips';
16
+ twips: number;
17
+ };
18
+ export interface PictureOutcome {
19
+ done: boolean;
20
+ /** Why the control was left as it was. */
21
+ note?: string;
22
+ }
23
+ /** Whether a control is bound to a picture through its tag (`od:Handler=picture` with an `od:xpath`). */
24
+ export declare function isPictureHandlerBound(control: ContentControl): boolean;
25
+ /** The tag's `width` parameter, read as `PictureWidth` says. */
26
+ export declare function pictureWidthOf(params: Map<string, string>): PictureWidth;
27
+ /**
28
+ * Binds one control to a value: base64 image data (a data URL accepted). With `keep`, the first
29
+ * `a:blip`'s `r:embed` is pointed at a new image part and the drawing is left as it is; a picture
30
+ * control holding no picture gets a new inline one at its natural size instead (docx4j's pre-v3
31
+ * fallback), a tag-bound control holding none is left and noted. Otherwise the control's content
32
+ * becomes one run holding a new inline picture, sized as `PictureWidth` says, in the first
33
+ * paragraph of a block-level control (its properties kept) or as a run-level control's content.
34
+ * An empty value, a value that is not an image this package reads (PNG, JPEG, GIF, BMP; SVG where
35
+ * the drawing is kept, since it has no pixel size to place a new picture at), and a body with no
36
+ * part each leave the control as it was, with a note. The image part the control showed before
37
+ * stays in the package, as docx4j leaves it.
38
+ */
39
+ export declare function bindPicture(control: ContentControl, value: string, width: PictureWidth, label: string): PictureOutcome;
40
+ /** Binds one tag-bound control (shape b): the entry's node, the tag's width. */
41
+ export declare function applyPictureHandlerTo(control: ContentControl, parts: CustomXmlPartLookup): PictureOutcome;
42
+ /** The picture pass over the tag-bound controls, after the text bindings: counted into the result, what was left said in its notes. */
43
+ export declare function applyPictureHandlersTo(controls: ContentControl[], parts: CustomXmlPartLookup, result: BindingResult): void;
44
+ /**
45
+ * The reverse direction for a mapped picture control (docx4j's `UpdateXmlFromDocumentSurface`
46
+ * skips pictures; Word 15 writes the picture back, check 38): the bytes of the image part the
47
+ * first `a:blip` embeds, as base64, into the node. Nothing is written when the node already holds
48
+ * those bytes (compared decoded, so Word's line-wrapped base64 counts as the same), when the
49
+ * control shows no picture, or when the blip's part is not an image part. Asynchronous, since a
50
+ * part's bytes may still be in the container.
51
+ */
52
+ export declare function updateFromPictureControl(control: ContentControl): Promise<boolean>;
53
+ /** `updateFromContentControls`' picture pass: the mapped picture controls, counted into the result. */
54
+ export declare function updateFromPictureControls(controls: ContentControl[], result: BindingResult): Promise<void>;