@docx4j/core-ts 0.5.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (207) hide show
  1. package/NOTICE +4 -0
  2. package/README.md +92 -4
  3. package/dist/html/convert.mjs +2 -1
  4. package/dist/html/elements.mjs +2 -1
  5. package/dist/model/color/ColorMath.d.mts +29 -0
  6. package/dist/model/color/ColorMath.mjs +146 -0
  7. package/dist/model/color/ColorTables.d.mts +6 -0
  8. package/dist/model/color/ColorTables.mjs +83 -0
  9. package/dist/model/color/DrawingMLColors.d.mts +52 -0
  10. package/dist/model/color/DrawingMLColors.mjs +374 -0
  11. package/dist/model/color/ResolvedColor.d.mts +36 -0
  12. package/dist/model/color/ResolvedColor.mjs +65 -0
  13. package/dist/model/color/SpreadsheetColors.d.mts +36 -0
  14. package/dist/model/color/SpreadsheetColors.mjs +84 -0
  15. package/dist/model/color/StyleFill.d.mts +28 -0
  16. package/dist/model/color/StyleFill.mjs +21 -0
  17. package/dist/model/color/ThemeColors.d.mts +86 -0
  18. package/dist/model/color/ThemeColors.mjs +304 -0
  19. package/dist/model/color/WordColors.d.mts +71 -0
  20. package/dist/model/color/WordColors.mjs +121 -0
  21. package/dist/model/color/index.d.mts +6 -0
  22. package/dist/model/color/index.mjs +30 -0
  23. package/dist/model/content/Body.d.mts +145 -23
  24. package/dist/model/content/Body.mjs +507 -150
  25. package/dist/model/content/Comment.d.mts +23 -5
  26. package/dist/model/content/Comment.mjs +207 -26
  27. package/dist/model/content/ContentControl.d.mts +198 -19
  28. package/dist/model/content/ContentControl.mjs +672 -99
  29. package/dist/model/content/Font.d.mts +70 -17
  30. package/dist/model/content/Font.mjs +119 -36
  31. package/dist/model/content/InlinePicture.d.mts +11 -0
  32. package/dist/model/content/InlinePicture.mjs +21 -1
  33. package/dist/model/content/List.d.mts +6 -3
  34. package/dist/model/content/List.mjs +18 -8
  35. package/dist/model/content/Paragraph.d.mts +200 -14
  36. package/dist/model/content/Paragraph.mjs +898 -82
  37. package/dist/model/content/Range.d.mts +227 -12
  38. package/dist/model/content/Range.mjs +785 -59
  39. package/dist/model/content/ResolvedView.d.mts +104 -0
  40. package/dist/model/content/ResolvedView.mjs +596 -0
  41. package/dist/model/content/Table.d.mts +29 -3
  42. package/dist/model/content/Table.mjs +135 -49
  43. package/dist/model/content/TrackedChange.d.mts +174 -15
  44. package/dist/model/content/TrackedChange.mjs +734 -170
  45. package/dist/model/content/comments.d.mts +20 -6
  46. package/dist/model/content/comments.mjs +67 -57
  47. package/dist/model/content/generalException.d.mts +18 -0
  48. package/dist/model/content/generalException.mjs +24 -0
  49. package/dist/model/content/index.d.mts +10 -3
  50. package/dist/model/content/index.mjs +10 -2
  51. package/dist/model/content/mathText.d.mts +21 -0
  52. package/dist/model/content/mathText.mjs +267 -0
  53. package/dist/model/content/officeText.d.mts +27 -0
  54. package/dist/model/content/officeText.mjs +74 -0
  55. package/dist/model/content/ooxml.mjs +12 -1
  56. package/dist/model/content/outline.d.mts +5 -0
  57. package/dist/model/content/outline.mjs +13 -0
  58. package/dist/model/content/paraIds.d.mts +5 -0
  59. package/dist/model/content/paraIds.mjs +54 -0
  60. package/dist/model/content/placeholders.d.mts +110 -0
  61. package/dist/model/content/placeholders.mjs +253 -0
  62. package/dist/model/content/search.d.mts +74 -0
  63. package/dist/model/content/search.mjs +255 -9
  64. package/dist/model/content/spanEdits.d.mts +159 -0
  65. package/dist/model/content/spanEdits.mjs +728 -0
  66. package/dist/model/content/story.d.mts +47 -0
  67. package/dist/model/content/story.mjs +95 -0
  68. package/dist/model/content/tableColumns.d.mts +1 -1
  69. package/dist/model/content/tableColumns.mjs +108 -80
  70. package/dist/model/content/tableRevisions.d.mts +11 -3
  71. package/dist/model/content/tableRevisions.mjs +72 -42
  72. package/dist/model/content/tableRules.d.mts +30 -0
  73. package/dist/model/content/tableRules.mjs +102 -0
  74. package/dist/model/content/textBoxes.d.mts +18 -0
  75. package/dist/model/content/textBoxes.mjs +105 -0
  76. package/dist/model/content/trackedChangeFilter.d.mts +39 -0
  77. package/dist/model/content/trackedChangeFilter.mjs +81 -0
  78. package/dist/model/content/tracking.d.mts +63 -6
  79. package/dist/model/content/tracking.mjs +242 -22
  80. package/dist/model/content/tree.d.mts +172 -24
  81. package/dist/model/content/tree.mjs +328 -54
  82. package/dist/model/content/wordRange.d.mts +48 -0
  83. package/dist/model/content/wordRange.mjs +97 -0
  84. package/dist/model/customxml/CustomXmlPartCollection.d.mts +30 -1
  85. package/dist/model/customxml/CustomXmlPartCollection.mjs +81 -12
  86. package/dist/model/customxml/XmlMapping.d.mts +32 -8
  87. package/dist/model/customxml/XmlMapping.mjs +64 -10
  88. package/dist/model/customxml/bindings.d.mts +33 -4
  89. package/dist/model/customxml/bindings.mjs +171 -45
  90. package/dist/model/customxml/index.d.mts +1 -0
  91. package/dist/model/customxml/index.mjs +1 -0
  92. package/dist/model/customxml/kinds.d.mts +8 -3
  93. package/dist/model/customxml/kinds.mjs +26 -5
  94. package/dist/model/customxml/opendope.d.mts +4 -1
  95. package/dist/model/customxml/opendope.mjs +9 -6
  96. package/dist/model/customxml/picturePlaceholder.d.mts +17 -0
  97. package/dist/model/customxml/picturePlaceholder.mjs +32 -0
  98. package/dist/model/customxml/pictures.d.mts +4 -3
  99. package/dist/model/customxml/pictures.mjs +22 -8
  100. package/dist/model/customxml/storeItems.d.mts +57 -0
  101. package/dist/model/customxml/storeItems.mjs +122 -0
  102. package/dist/model/customxml/xhtml.mjs +23 -22
  103. package/dist/model/dateZone.d.mts +25 -0
  104. package/dist/model/dateZone.mjs +57 -0
  105. package/dist/model/index.d.mts +3 -0
  106. package/dist/model/index.mjs +5 -0
  107. package/dist/model/listnumbering/Emulator.d.mts +37 -5
  108. package/dist/model/listnumbering/Emulator.mjs +63 -9
  109. package/dist/model/properties/PropertyResolver.d.mts +74 -2
  110. package/dist/model/properties/PropertyResolver.mjs +133 -5
  111. package/dist/model/properties/catalogue.d.mts +19 -5
  112. package/dist/model/properties/catalogue.mjs +41 -15
  113. package/dist/model/properties/index.d.mts +1 -1
  114. package/dist/model/properties/log.d.mts +1 -1
  115. package/dist/model/properties/log.mjs +5 -5
  116. package/dist/model/properties/styleUtil.d.mts +3 -1
  117. package/dist/model/properties/styleUtil.mjs +20 -24
  118. package/dist/model/properties/table.mjs +4 -2
  119. package/dist/model/text/CaseMapping.d.mts +38 -0
  120. package/dist/model/text/CaseMapping.mjs +86 -0
  121. package/dist/model/text/Extractor.d.mts +349 -0
  122. package/dist/model/text/Extractor.mjs +2406 -0
  123. package/dist/model/text/LinkTargets.d.mts +45 -0
  124. package/dist/model/text/LinkTargets.mjs +170 -0
  125. package/dist/model/text/MarkdownExporter.d.mts +140 -0
  126. package/dist/model/text/MarkdownExporter.mjs +255 -0
  127. package/dist/model/text/PlainText.d.mts +29 -0
  128. package/dist/model/text/PlainText.mjs +542 -0
  129. package/dist/model/text/TextContext.d.mts +256 -0
  130. package/dist/model/text/TextContext.mjs +600 -0
  131. package/dist/model/text/TextOptions.d.mts +233 -0
  132. package/dist/model/text/TextOptions.mjs +419 -0
  133. package/dist/model/text/TextResult.d.mts +621 -0
  134. package/dist/model/text/TextResult.mjs +629 -0
  135. package/dist/model/text/Texts.d.mts +99 -0
  136. package/dist/model/text/Texts.mjs +187 -0
  137. package/dist/model/text/WmlToMarkdown.d.mts +120 -0
  138. package/dist/model/text/WmlToMarkdown.mjs +936 -0
  139. package/dist/model/text/caseMapping.generated.d.mts +7 -0
  140. package/dist/model/text/caseMapping.generated.mjs +214 -0
  141. package/dist/model/text/commonmark.d.mts +121 -0
  142. package/dist/model/text/commonmark.mjs +655 -0
  143. package/dist/model/text/index.d.mts +8 -0
  144. package/dist/model/text/index.mjs +9 -0
  145. package/dist/model/text/normalize.d.mts +8 -0
  146. package/dist/model/text/normalize.mjs +13 -0
  147. package/dist/model/text/outline.d.mts +4 -0
  148. package/dist/model/text/outline.mjs +71 -0
  149. package/dist/model/wordIntegers.d.mts +29 -0
  150. package/dist/model/wordIntegers.mjs +78 -0
  151. package/dist/office-js/document.mjs +15 -1
  152. package/dist/office-js/fragment.d.mts +7 -5
  153. package/dist/office-js/fragment.mjs +63 -14
  154. package/dist/office-js/proxy.d.mts +8 -0
  155. package/dist/office-js/proxy.mjs +29 -0
  156. package/dist/office-js/run.mjs +2 -0
  157. package/dist/office-js/supported.generated.mjs +16 -0
  158. package/dist/office-js/toApiScript.mjs +29 -12
  159. package/dist/opc/DirectoryPartStore.d.mts +6 -2
  160. package/dist/opc/DirectoryPartStore.mjs +11 -4
  161. package/dist/opc/FlatOpcPartStore.d.mts +4 -2
  162. package/dist/opc/FlatOpcPartStore.mjs +11 -8
  163. package/dist/opc/Load.d.mts +32 -0
  164. package/dist/opc/Load.mjs +115 -5
  165. package/dist/opc/PartStore.d.mts +34 -1
  166. package/dist/opc/PartStore.mjs +71 -8
  167. package/dist/opc/Save.d.mts +32 -0
  168. package/dist/opc/Save.mjs +110 -4
  169. package/dist/opc/ZipPartStore.d.mts +4 -2
  170. package/dist/opc/ZipPartStore.mjs +12 -7
  171. package/dist/opc/exceptions.d.mts +140 -0
  172. package/dist/opc/exceptions.mjs +108 -0
  173. package/dist/opc/index.d.mts +8 -5
  174. package/dist/opc/index.mjs +5 -3
  175. package/dist/opc/mce/preprocessor.d.mts +141 -5
  176. package/dist/opc/mce/preprocessor.mjs +119 -18
  177. package/dist/opc/mce/understood.d.mts +3 -11
  178. package/dist/opc/mce/understood.mjs +27 -23
  179. package/dist/opc/repair/breaks.d.mts +23 -0
  180. package/dist/opc/repair/breaks.mjs +288 -0
  181. package/dist/opc/repair/hosts.d.mts +12 -0
  182. package/dist/opc/repair/hosts.mjs +33 -0
  183. package/dist/opc/repair/mainRoot.d.mts +19 -0
  184. package/dist/opc/repair/mainRoot.mjs +168 -0
  185. package/dist/opc/repair/unknownElements.d.mts +8 -0
  186. package/dist/opc/repair/unknownElements.mjs +180 -0
  187. package/dist/opc/repair/wmlNames.generated.d.mts +2 -0
  188. package/dist/opc/repair/wmlNames.generated.mjs +80 -0
  189. package/dist/opc/skipped.d.mts +50 -0
  190. package/dist/opc/skipped.mjs +140 -0
  191. package/dist/packages/OpcPackage.d.mts +110 -4
  192. package/dist/packages/OpcPackage.mjs +123 -7
  193. package/dist/packages/WordprocessingMLPackage.d.mts +45 -8
  194. package/dist/packages/WordprocessingMLPackage.mjs +158 -21
  195. package/dist/packages/index.d.mts +1 -1
  196. package/dist/parts/DefaultXmlPart.mjs +2 -2
  197. package/dist/parts/Part.d.mts +14 -2
  198. package/dist/parts/RelationshipsPart.d.mts +15 -0
  199. package/dist/parts/RelationshipsPart.mjs +66 -1
  200. package/dist/parts/XmlPart.d.mts +63 -32
  201. package/dist/parts/XmlPart.mjs +240 -96
  202. package/dist/parts/wml/comments.mjs +30 -0
  203. package/dist/parts/wml/index.d.mts +12 -1
  204. package/dist/parts/wml/index.mjs +37 -1
  205. package/dist/xml/dom.d.mts +7 -0
  206. package/dist/xml/dom.mjs +18 -0
  207. package/package.json +2 -2
@@ -0,0 +1,27 @@
1
+ import type * as wml from '@docx4j/generated-objects-ts/modules/org_docx4j_wml';
2
+ import { type Element } from './tree.mjs';
3
+ /** The text without the characters XML 1.0 excludes (E3). */
4
+ export declare function dropExcluded(text: string): string;
5
+ /** True when the text holds a character a writer writes as an element. */
6
+ export declare function holdsElementCharacters(text: string): boolean;
7
+ /**
8
+ * How a writer takes a line feed and a carriage return:
9
+ * - `paragraphs`: a paragraph break each, `\r\n` one (E1: `insertText` and every writer through `Paragraph.splice`);
10
+ * the caller splits the text into lines (`linesOf`);
11
+ * - `one-paragraph`: `\n` and `\r\n` a `w:br`, `\r` a `w:cr` (E2: `insertParagraph`, `Paragraph.text =`), a recorded
12
+ * departure from Word, which writes their escapes as text;
13
+ * - `multi-line`: each a `w:br` (a multi-line plain-text control, or a bound block control of one: section 61 item 3);
14
+ * - `single-line`: none, and no break either (a single-line plain-text control: F3).
15
+ */
16
+ export type LineBreaks = 'paragraphs' | 'one-paragraph' | 'multi-line' | 'single-line';
17
+ /** The text a writer of that kind writes, its line feeds and carriage returns turned into its characters. */
18
+ export declare function forWriter(text: string, lineBreaks: LineBreaks): string;
19
+ /** The text's lines, at each line feed, carriage return or `\r\n` (E1). */
20
+ export declare function linesOf(text: string): string[];
21
+ /** A run's items for a text: a `w:t` for each plain stretch, the element each mapped character reads as. */
22
+ export declare function officeItems(text: string): Element[];
23
+ /**
24
+ * A run of text a writer keeps in one paragraph (E2: `insertParagraph`, `Paragraph.text =`), or in a plain-text control
25
+ * as its kind takes it: the excluded characters dropped, every other character that is an element written as it.
26
+ */
27
+ export declare function oneParagraphRun(text: string, rPr?: wml.RPr, lineBreaks?: LineBreaks): Element<wml.R>;
@@ -0,0 +1,74 @@
1
+ // Office JS's characters written back (CR-002 section 60). The readers give Word's character stream (CR-012 phase 4,
2
+ // `officeItemText`): a `w:br` U+000B, a page break U+000C, a column break U+000E, a `w:cr` U+000D, the non-breaking and
3
+ // optional hyphens U+001E and U+001F, a note reference U+0002. A writer turns each back into the element it reads as
4
+ // (D1, as Word 365's writers do in check 55), drops the characters XML 1.0 excludes (E3: Word drops them too), and
5
+ // takes a line feed or carriage return as its kind of writer does: a paragraph break for `insertText` (E1), a break
6
+ // for `insertParagraph` (E2), a break or nothing in a bound or plain-text control (section 61 item 3, F3).
7
+ import * as el from '@docx4j/generated-objects-ts/el/org_docx4j_wml';
8
+ import { t as textItem, br as breakItem, tab as tabItem } from '@docx4j/generated-objects-ts/builders/wml';
9
+ import { runOf } from './tree.mjs';
10
+ /**
11
+ * The characters XML 1.0 excludes that are no element: U+0002 (a note reference, which no text makes) and the other C0
12
+ * controls but tab, line feed, carriage return and the five that are elements, U+FFFE, U+FFFF and an unpaired surrogate.
13
+ */
14
+ const EXCLUDED = /[\u0000-\u0008\u000F-\u001D￾￿]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g;
15
+ /** The characters a writer writes as elements: tab, the three breaks, a carriage return, a line feed, the two hyphens. */
16
+ const ELEMENT_CHARACTERS = /[\t\n\r\u000B\u000C\u000E\u001E\u001F]/;
17
+ /** The text without the characters XML 1.0 excludes (E3). */
18
+ export function dropExcluded(text) {
19
+ return text.replace(EXCLUDED, '');
20
+ }
21
+ /** True when the text holds a character a writer writes as an element. */
22
+ export function holdsElementCharacters(text) {
23
+ return ELEMENT_CHARACTERS.test(text);
24
+ }
25
+ /** The text a writer of that kind writes, its line feeds and carriage returns turned into its characters. */
26
+ export function forWriter(text, lineBreaks) {
27
+ switch (lineBreaks) {
28
+ case 'one-paragraph': return text.replace(/\r\n|\n/g, '\u000B');
29
+ case 'multi-line': return text.replace(/\r\n|\r|\n/g, '\u000B');
30
+ case 'single-line': return text.replace(/\r\n|[\r\n\u000B\u000C\u000E]/g, '');
31
+ default: return text;
32
+ }
33
+ }
34
+ /** The text's lines, at each line feed, carriage return or `\r\n` (E1). */
35
+ export function linesOf(text) {
36
+ return text.split(/\r\n|\r|\n/);
37
+ }
38
+ /** The element a character reads as (`officeItemText` in reverse); a line feed a `w:br`. */
39
+ function elementFor(c) {
40
+ switch (c) {
41
+ case '\t': return tabItem();
42
+ case '\u000C': return breakItem('page');
43
+ case '\u000E': return breakItem('column');
44
+ case '\r': return el.cr({});
45
+ case '\u001E': return el.noBreakHyphen({});
46
+ case '\u001F': return el.softHyphen({});
47
+ default: return breakItem(); // U+000B, and a line feed a writer left
48
+ }
49
+ }
50
+ /** A run's items for a text: a `w:t` for each plain stretch, the element each mapped character reads as. */
51
+ export function officeItems(text) {
52
+ const out = [];
53
+ let plain = '';
54
+ for (const c of text) {
55
+ if (ELEMENT_CHARACTERS.test(c)) {
56
+ if (plain !== '')
57
+ out.push(textItem(plain));
58
+ plain = '';
59
+ out.push(elementFor(c));
60
+ }
61
+ else
62
+ plain += c;
63
+ }
64
+ if (plain !== '')
65
+ out.push(textItem(plain));
66
+ return out;
67
+ }
68
+ /**
69
+ * A run of text a writer keeps in one paragraph (E2: `insertParagraph`, `Paragraph.text =`), or in a plain-text control
70
+ * as its kind takes it: the excluded characters dropped, every other character that is an element written as it.
71
+ */
72
+ export function oneParagraphRun(text, rPr, lineBreaks = 'one-paragraph') {
73
+ return runOf(officeItems(forWriter(dropExcluded(text), lineBreaks)), rPr);
74
+ }
@@ -7,6 +7,8 @@
7
7
  // `insertXml` takes; the two share this module.
8
8
  import { wml as parseFragment, walkAll } from '@docx4j/generated-objects-ts/builders/wml';
9
9
  import { Docx4JException } from '../../opc/exceptions.mjs';
10
+ import { repairBreaksOutsideRuns } from '../../opc/repair/breaks.mjs';
11
+ import { unknownElementsIn } from '../../opc/repair/unknownElements.mjs';
10
12
  import { PartName } from '../../opc/PartName.mjs';
11
13
  import { ContentTypes } from '../../opc/ContentTypes.mjs';
12
14
  import { BinaryPart, ImagePart } from '../../parts/BinaryPart.mjs';
@@ -25,6 +27,12 @@ export function isFlatOpc(ooxml) {
25
27
  const REL_ATTRIBUTES = ['embed', 'link', 'id', 'href', 'pict', 'dm', 'lo', 'qs', 'cs', 'topLeft', 'topRight', 'bottomLeft', 'bottomRight'];
26
28
  /** The relationships namespace, for the same attributes on DOM nodes kept as `any` content. */
27
29
  const R_NS = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships';
30
+ /** CR-026 D12: a caller's own fragment holding an element Word doesn't know is refused, where a file arriving is read as Word reads it. */
31
+ function refuseUnknown(names) {
32
+ if (names.length === 0)
33
+ return;
34
+ throw new Docx4JException(`insertOoxml refused: the content holds ${names.length === 1 ? 'an element' : 'elements'} Word doesn't know (${[...new Set(names)].join(', ')})`);
35
+ }
28
36
  /**
29
37
  * The block-level content of `ooxml`, ready to insert: a flat OPC package's body content with
30
38
  * its parts copied and its references rewritten, or a fragment's elements.
@@ -32,7 +40,9 @@ const R_NS = 'http://schemas.openxmlformats.org/officeDocument/2006/relationship
32
40
  export async function contentOf(ooxml, options = {}) {
33
41
  var _a, _b;
34
42
  if (!isFlatOpc(ooxml)) {
35
- return parseFragment(ooxml, { wrapper: 'body', preprocess: options.preprocess });
43
+ // a w:br outside a w:r read as Word reads it first, as on load (CR-021 D1): a created package has no hook
44
+ const preprocess = options.preprocess;
45
+ return parseFragment(ooxml, { wrapper: 'body', preprocess: (doc) => { refuseUnknown(unknownElementsIn(doc).map((e) => e.nodeName)); repairBreaksOutsideRuns(doc); preprocess === null || preprocess === void 0 ? void 0 : preprocess(doc); } });
36
46
  }
37
47
  const target = options.target;
38
48
  if (!target)
@@ -43,6 +53,7 @@ export async function contentOf(ooxml, options = {}) {
43
53
  const source = await WordprocessingMLPackage.load(ooxml, options.preprocess ? { preprocessor: options.preprocess } : {});
44
54
  const main = source.getMainDocumentPart();
45
55
  const document = await main.getContents();
56
+ refuseUnknown(main.warnings.flatMap((w) => (w.code === 'part.skipped' && w.kind === 'element' ? [w.name] : [])));
46
57
  const content = ((_b = (_a = document.body) === null || _a === void 0 ? void 0 : _a.content) !== null && _b !== void 0 ? _b : []);
47
58
  if (content.length === 0)
48
59
  return [];
@@ -0,0 +1,5 @@
1
+ import type { Body, Outline } from './Body.mjs';
2
+ /** What `Body.outline` asks of the text model: the outline of a body, or of its top-level blocks at these indices, under these options and context. */
3
+ export type OutlineReader = (body: Body, options: unknown, context: unknown, blocks?: readonly number[]) => Promise<Outline>;
4
+ export declare function setOutlineReader(implementation: OutlineReader): void;
5
+ export declare function outlineReader(): OutlineReader;
@@ -0,0 +1,13 @@
1
+ // The outline's reader (CR-012 D3): the text model's, which this module can't import, since the text model
2
+ // imports the content API. The text model registers it (`model/text/outline.mts`), and the package classes
3
+ // load that module, as the comment parts register themselves (CR-002 phase G).
4
+ import { Docx4JException } from '../../opc/exceptions.mjs';
5
+ let reader;
6
+ export function setOutlineReader(implementation) {
7
+ reader = implementation;
8
+ }
9
+ export function outlineReader() {
10
+ if (!reader)
11
+ throw new Docx4JException('The text model is not loaded: import @docx4j/core-ts, @docx4j/core-ts/model or @docx4j/core-ts/packages');
12
+ return reader;
13
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Gives every paragraph and row in `added`, at any depth, a w14:paraId, where the story `within` stands in uses them (CR-002
3
+ * section 66). A document that uses none stays so. `added` has to be in the tree already, or `within` be what it goes in.
4
+ */
5
+ export declare function assignParaIds(within: object, added: Iterable<unknown>): void;
@@ -0,0 +1,54 @@
1
+ // w14:paraId on what the content API adds. In some files, one paragraph or row without an id makes Word 365 renumber the whole
2
+ // part at the next open (check 79: 79d-table, 79f-one-missing, 79f-row-ids; check 80), which breaks a `w14:` address held across
3
+ // that open. In others it fills the gap, and what separates them isn't known (CR-002 section 66). With an id on everything nothing
4
+ // is left to chance, so a new paragraph or row takes one wherever the story already uses them.
5
+ import { walk } from '@docx4j/generated-objects-ts/builders/wml';
6
+ const P = 'org_docx4j_wml.P';
7
+ const TR = 'org_docx4j_wml.Tr';
8
+ const holdsAnId = (value) => {
9
+ const type = value.TYPE_NAME;
10
+ return type === P || type === TR;
11
+ };
12
+ /** The top of the tree `within` stands in, up `PARENT`: the whole story, so a cell's own body sees the document's ids. */
13
+ function storyOf(within) {
14
+ let top = within;
15
+ for (let guard = 0; top.PARENT !== undefined && guard < 4096; guard++)
16
+ top = top.PARENT;
17
+ return top;
18
+ }
19
+ /** The ids the story uses. A paragraph's own content isn't entered: what it holds matters only to a clash, one in two billion. */
20
+ function idsIn(story) {
21
+ const used = new Set();
22
+ walk(story, (value) => {
23
+ if (!holdsAnId(value))
24
+ return undefined;
25
+ if (value.paraId)
26
+ used.add(value.paraId.toUpperCase());
27
+ return value.TYPE_NAME === P ? false : undefined;
28
+ });
29
+ return used;
30
+ }
31
+ function freeId(used) {
32
+ let id;
33
+ do {
34
+ id = Math.floor(Math.random() * 0x7fffffff).toString(16).toUpperCase().padStart(8, '0');
35
+ } while (used.has(id));
36
+ used.add(id);
37
+ return id;
38
+ }
39
+ /**
40
+ * Gives every paragraph and row in `added`, at any depth, a w14:paraId, where the story `within` stands in uses them (CR-002
41
+ * section 66). A document that uses none stays so. `added` has to be in the tree already, or `within` be what it goes in.
42
+ */
43
+ export function assignParaIds(within, added) {
44
+ const used = idsIn(storyOf(within));
45
+ if (used.size === 0)
46
+ return;
47
+ for (const block of added) {
48
+ walk(block, (value) => {
49
+ if (holdsAnId(value) && !value.paraId)
50
+ value.paraId = freeId(used);
51
+ return undefined;
52
+ });
53
+ }
54
+ }
@@ -0,0 +1,110 @@
1
+ import type * as wml from '@docx4j/generated-objects-ts/modules/org_docx4j_wml';
2
+ import { type Element } from './tree.mjs';
3
+ import type { XmlPart } from '../../parts/XmlPart.mjs';
4
+ import type { Part } from '../../parts/Part.mjs';
5
+ /** Word's default placeholder entry, which a control Word makes names (checks 64 to 66). */
6
+ export declare const DEFAULT_PLACEHOLDER_ENTRY = "DefaultPlaceholder_-1854013440";
7
+ /** The default entry's text, as Word 365 writes it. */
8
+ export declare const DEFAULT_PLACEHOLDER_TEXT = "Click or tap here to enter text.";
9
+ /** A default placeholder entry: its name and its text. */
10
+ export interface DefaultEntry {
11
+ name: string;
12
+ text: string;
13
+ }
14
+ /**
15
+ * Word's default placeholder entry for each kind of control that has one, as Word 365 makes them from the Developer tab
16
+ * (Jason's `placeholders.docx`, 2026-10-25; CR-002 section 63.22, as docx4j's CR-063 D8 has it): each in the glossary
17
+ * with the default entry's `w:docPartPr`, its body one run in the Placeholder Text style. A picture, a check box and a
18
+ * repeating section have none; the group, citation, equation, bibliography and `w:docPartObj` kinds are unmeasured.
19
+ */
20
+ export declare const DEFAULT_ENTRIES: {
21
+ readonly text: {
22
+ readonly name: "DefaultPlaceholder_-1854013440";
23
+ readonly text: "Click or tap here to enter text.";
24
+ };
25
+ readonly date: {
26
+ readonly name: "DefaultPlaceholder_-1854013437";
27
+ readonly text: "Click or tap to enter a date.";
28
+ };
29
+ readonly list: {
30
+ readonly name: "DefaultPlaceholder_-1854013438";
31
+ readonly text: "Choose an item.";
32
+ };
33
+ readonly buildingBlock: {
34
+ readonly name: "DefaultPlaceholder_-1854013436";
35
+ readonly text: "Choose a building block.";
36
+ };
37
+ readonly repeatingSectionItem: {
38
+ readonly name: "DefaultPlaceholder_-1854013435";
39
+ readonly text: "Enter any content that you want to repeat, including other content controls. You can also insert this control around table rows in order to repeat parts of a table.";
40
+ };
41
+ };
42
+ /** The default entry Word gives a control of this kind (`DEFAULT_ENTRIES`); undefined for a kind it gives none. */
43
+ export declare function defaultEntryFor(sdtPr: wml.SdtPr | undefined): DefaultEntry | undefined;
44
+ /** The glossary entry a control's `w:placeholder` names; undefined where it names none (the bare form). */
45
+ export declare function placeholderEntryOf(sdtPr: wml.SdtPr | undefined): string | undefined;
46
+ /**
47
+ * Names `entry` in the control's `w:placeholder`, where Word writes it: after `w:id`, before `w:showingPlcHdr` and the
48
+ * binding (check 64's controls, as Word saved them).
49
+ */
50
+ export declare function namePlaceholderEntry(sdtPr: wml.SdtPr, entry: string): void;
51
+ /** A new entry's name, as Word names one: 32 upper-case hex digits, a new GUID's (check 65). */
52
+ export declare function newEntryName(): string;
53
+ /** What a package's glossary gives: an entry's text by name. */
54
+ export interface GlossaryHost {
55
+ getMainDocumentPart(): {
56
+ glossaryDocumentPart: XmlPart<wml.GlossaryDocument> | undefined;
57
+ addTargetPart(part: Part): unknown;
58
+ };
59
+ }
60
+ /**
61
+ * The placeholder entries of a package's glossary: those its glossary document part holds, read privately (the part
62
+ * stays untouched), and those written since, which go into the glossary when the package is saved (`flush`).
63
+ */
64
+ export declare class GlossaryEntries {
65
+ /** Entries by name, as the glossary part was last read; undefined until `load`. */
66
+ private read;
67
+ /** The part they were read from. */
68
+ private source;
69
+ /** Entries to write on save: name, text. */
70
+ private readonly pending;
71
+ /** Reads the glossary's entries, privately; again only where the part changed. */
72
+ load(glossary: XmlPart<wml.GlossaryDocument> | undefined): Promise<void>;
73
+ /**
74
+ * An entry's text, `null` where neither the glossary nor this session holds it, as Office JS reads it (check 65). An
75
+ * entry's paragraphs are a `\r` apart, as a control's are.
76
+ */
77
+ textOf(name: string): string | null;
78
+ /**
79
+ * An entry's body, the blocks of its `w:docPartBody` (not copies), `null` where neither the glossary nor this session
80
+ * holds it; an entry written this session is one paragraph, one run of its text in the Placeholder Text style.
81
+ */
82
+ bodyOf(name: string): Element[] | null;
83
+ /** True once the glossary has been read (`load`): until then an entry it holds reads as missing. */
84
+ get loaded(): boolean;
85
+ private entry;
86
+ /** Whether the glossary holds an entry of this name, or one is waiting to be written. */
87
+ has(name: string): boolean;
88
+ /** An entry to write on save, where the glossary doesn't hold one of that name. */
89
+ require(name: string, text: string): void;
90
+ /** True while entries wait for the save. */
91
+ get waiting(): boolean;
92
+ /** Entries waiting for the save, for a clone. */
93
+ copyTo(target: GlossaryEntries): void;
94
+ /**
95
+ * Writes the waiting entries into the glossary, making it where the file has none (D9 (a): the glossary document
96
+ * part and a styles part holding Placeholder Text, related from it), and puts Placeholder Text into a glossary's
97
+ * styles part that lacks it: an entry whose style its glossary lacks loses it in Word's save (check 65's 65b).
98
+ */
99
+ flush(host: GlossaryHost, make: {
100
+ glossary: () => XmlPart<wml.GlossaryDocument>;
101
+ styles: () => XmlPart<wml.Styles>;
102
+ }): Promise<void>;
103
+ }
104
+ /**
105
+ * What a control shows of an entry (CR-002 section 63.18, as revised by 63.21): a copy of each of its body's paragraphs'
106
+ * run-level content, every run as the glossary holds it. Word brought an entry back so, the control's own run properties
107
+ * not over it (check 68, cases 21 to 24), and doesn't restyle it (check 65's 65b, case 9). The paragraphs' properties stay
108
+ * in the glossary, whose styles they name.
109
+ */
110
+ export declare function entryContentOf(body: Element[]): Element[][];
@@ -0,0 +1,253 @@
1
+ import * as el from '@docx4j/generated-objects-ts/el/org_docx4j_wml';
2
+ import * as f from '@docx4j/generated-objects-ts/factory/org_docx4j_wml';
3
+ import { sdtProperty, sdtKindOf, textOf } from '@docx4j/generated-objects-ts/builders/wml';
4
+ import { deepCopy } from '@docx4j/generated-objects-ts';
5
+ import { linkParents } from './tree.mjs';
6
+ import { uuid } from '../customxml/CustomXmlPartCollection.mjs';
7
+ import { PLACEHOLDER_STYLE, placeholderRun } from '../customxml/bindings.mjs';
8
+ import { placeholderStyle } from '../../parts/wml/placeholderStyle.mjs';
9
+ /** Word's default placeholder entry, which a control Word makes names (checks 64 to 66). */
10
+ export const DEFAULT_PLACEHOLDER_ENTRY = 'DefaultPlaceholder_-1854013440';
11
+ /** The default entry's text, as Word 365 writes it. */
12
+ export const DEFAULT_PLACEHOLDER_TEXT = 'Click or tap here to enter text.';
13
+ /**
14
+ * Word's default placeholder entry for each kind of control that has one, as Word 365 makes them from the Developer tab
15
+ * (Jason's `placeholders.docx`, 2026-10-25; CR-002 section 63.22, as docx4j's CR-063 D8 has it): each in the glossary
16
+ * with the default entry's `w:docPartPr`, its body one run in the Placeholder Text style. A picture, a check box and a
17
+ * repeating section have none; the group, citation, equation, bibliography and `w:docPartObj` kinds are unmeasured.
18
+ */
19
+ export const DEFAULT_ENTRIES = {
20
+ text: { name: DEFAULT_PLACEHOLDER_ENTRY, text: DEFAULT_PLACEHOLDER_TEXT },
21
+ date: { name: 'DefaultPlaceholder_-1854013437', text: 'Click or tap to enter a date.' },
22
+ list: { name: 'DefaultPlaceholder_-1854013438', text: 'Choose an item.' },
23
+ buildingBlock: { name: 'DefaultPlaceholder_-1854013436', text: 'Choose a building block.' },
24
+ repeatingSectionItem: {
25
+ name: 'DefaultPlaceholder_-1854013435',
26
+ text: 'Enter any content that you want to repeat, including other content controls. You can also insert this control around table rows in order to repeat parts of a table.',
27
+ },
28
+ };
29
+ /** The default entry Word gives a control of this kind (`DEFAULT_ENTRIES`); undefined for a kind it gives none. */
30
+ export function defaultEntryFor(sdtPr) {
31
+ switch (sdtKindOf(sdtPr)) {
32
+ case 'RichText':
33
+ case 'PlainText': return DEFAULT_ENTRIES.text;
34
+ case 'DatePicker': return DEFAULT_ENTRIES.date;
35
+ case 'DropDownList':
36
+ case 'ComboBox': return DEFAULT_ENTRIES.list;
37
+ case 'BuildingBlockGallery': return sdtProperty(sdtPr, 'docPartList') ? DEFAULT_ENTRIES.buildingBlock : undefined;
38
+ case 'RepeatingSectionItem': return DEFAULT_ENTRIES.repeatingSectionItem;
39
+ default: return undefined;
40
+ }
41
+ }
42
+ /** The glossary entry a control's `w:placeholder` names; undefined where it names none (the bare form). */
43
+ export function placeholderEntryOf(sdtPr) {
44
+ var _a, _b, _c;
45
+ const value = (_a = sdtProperty(sdtPr, 'placeholder')) === null || _a === void 0 ? void 0 : _a.value;
46
+ return value === undefined ? undefined : (_c = (_b = value.docPart) === null || _b === void 0 ? void 0 : _b.val) !== null && _c !== void 0 ? _c : '';
47
+ }
48
+ /**
49
+ * Names `entry` in the control's `w:placeholder`, where Word writes it: after `w:id`, before `w:showingPlcHdr` and the
50
+ * binding (check 64's controls, as Word saved them).
51
+ */
52
+ export function namePlaceholderEntry(sdtPr, entry) {
53
+ var _a;
54
+ const items = ((_a = sdtPr.rPrOrAliasOrLock) !== null && _a !== void 0 ? _a : (sdtPr.rPrOrAliasOrLock = []));
55
+ const element = el.placeholder(f.createCTPlaceholder({ docPart: f.createCTPlaceholderDocPart({ val: entry }) }));
56
+ const at = items.findIndex((i) => i.name.localPart === 'placeholder');
57
+ if (at >= 0) {
58
+ items[at] = element;
59
+ linkParents(element, sdtPr);
60
+ return;
61
+ }
62
+ const after = new Set(['showingPlcHdr', 'dataBinding', 'temporary']);
63
+ const before = items.findIndex((i) => after.has(i.name.localPart));
64
+ items.splice(before < 0 ? items.length : before, 0, element);
65
+ linkParents(element, sdtPr);
66
+ }
67
+ /** A new entry's name, as Word names one: 32 upper-case hex digits, a new GUID's (check 65). */
68
+ export function newEntryName() {
69
+ return uuid().replace(/-/g, '').toUpperCase();
70
+ }
71
+ /**
72
+ * The placeholder entries of a package's glossary: those its glossary document part holds, read privately (the part
73
+ * stays untouched), and those written since, which go into the glossary when the package is saved (`flush`).
74
+ */
75
+ export class GlossaryEntries {
76
+ constructor() {
77
+ /** Entries to write on save: name, text. */
78
+ this.pending = new Map();
79
+ }
80
+ /** Reads the glossary's entries, privately; again only where the part changed. */
81
+ async load(glossary) {
82
+ if (glossary === this.source && this.read !== undefined)
83
+ return;
84
+ this.source = glossary;
85
+ this.read = glossary ? entriesOf(await glossary.readContents().catch(() => undefined)) : new Map();
86
+ }
87
+ /**
88
+ * An entry's text, `null` where neither the glossary nor this session holds it, as Office JS reads it (check 65). An
89
+ * entry's paragraphs are a `\r` apart, as a control's are.
90
+ */
91
+ textOf(name) {
92
+ var _a, _b;
93
+ return (_b = (_a = this.entry(name)) === null || _a === void 0 ? void 0 : _a.text) !== null && _b !== void 0 ? _b : null;
94
+ }
95
+ /**
96
+ * An entry's body, the blocks of its `w:docPartBody` (not copies), `null` where neither the glossary nor this session
97
+ * holds it; an entry written this session is one paragraph, one run of its text in the Placeholder Text style.
98
+ */
99
+ bodyOf(name) {
100
+ var _a, _b;
101
+ return (_b = (_a = this.entry(name)) === null || _a === void 0 ? void 0 : _a.body) !== null && _b !== void 0 ? _b : null;
102
+ }
103
+ /** True once the glossary has been read (`load`): until then an entry it holds reads as missing. */
104
+ get loaded() {
105
+ var _a;
106
+ return this.read !== undefined || ((_a = this.source) === null || _a === void 0 ? void 0 : _a.isUnmarshalled) === true;
107
+ }
108
+ entry(name) {
109
+ var _a;
110
+ const pending = this.pending.get(name);
111
+ if (pending !== undefined)
112
+ return { text: pending, body: [el.p({ content: [placeholderRun(undefined, pending)] })] };
113
+ const part = this.source;
114
+ if (part === null || part === void 0 ? void 0 : part.isUnmarshalled)
115
+ return entriesOf(part.contents).get(name);
116
+ return (_a = this.read) === null || _a === void 0 ? void 0 : _a.get(name);
117
+ }
118
+ /** Whether the glossary holds an entry of this name, or one is waiting to be written. */
119
+ has(name) {
120
+ return this.textOf(name) !== null;
121
+ }
122
+ /** An entry to write on save, where the glossary doesn't hold one of that name. */
123
+ require(name, text) {
124
+ if (!this.has(name))
125
+ this.pending.set(name, text);
126
+ }
127
+ /** True while entries wait for the save. */
128
+ get waiting() {
129
+ return this.pending.size > 0;
130
+ }
131
+ /** Entries waiting for the save, for a clone. */
132
+ copyTo(target) {
133
+ for (const [name, text] of this.pending)
134
+ target.pending.set(name, text);
135
+ }
136
+ /**
137
+ * Writes the waiting entries into the glossary, making it where the file has none (D9 (a): the glossary document
138
+ * part and a styles part holding Placeholder Text, related from it), and puts Placeholder Text into a glossary's
139
+ * styles part that lacks it: an entry whose style its glossary lacks loses it in Word's save (check 65's 65b).
140
+ */
141
+ async flush(host, make) {
142
+ var _a;
143
+ if (this.pending.size === 0)
144
+ return;
145
+ const main = host.getMainDocumentPart();
146
+ let glossary = main.glossaryDocumentPart;
147
+ if (!glossary) {
148
+ glossary = make.glossary();
149
+ glossary.setContents(f.createGlossaryDocument({ docParts: f.createCTDocParts({ docPart: [] }) }));
150
+ main.addTargetPart(glossary);
151
+ }
152
+ const doc = await glossary.getContents();
153
+ const docParts = ((_a = doc.docParts) !== null && _a !== void 0 ? _a : (doc.docParts = f.createCTDocParts({ docPart: [] })));
154
+ linkParents(docParts, doc);
155
+ const present = entriesOf(doc);
156
+ for (const [name, text] of this.pending) {
157
+ if (present.has(name))
158
+ continue;
159
+ const part = docPartOf(name, text);
160
+ docParts.docPart.push(part);
161
+ linkParents(part, docParts);
162
+ }
163
+ this.pending.clear();
164
+ this.source = glossary;
165
+ await ensureGlossaryStyles(glossary, make.styles);
166
+ }
167
+ }
168
+ /** A glossary's entries by name. */
169
+ function entriesOf(doc) {
170
+ var _a, _b, _c, _d, _e;
171
+ const out = new Map();
172
+ for (const part of (_b = (_a = doc === null || doc === void 0 ? void 0 : doc.docParts) === null || _a === void 0 ? void 0 : _a.docPart) !== null && _b !== void 0 ? _b : []) {
173
+ const name = (_c = part.docPartPr) === null || _c === void 0 ? void 0 : _c.nameOrStyleOrCategory.find((i) => i.TYPE_NAME === 'org_docx4j_wml.CTDocPartName');
174
+ if (!(name === null || name === void 0 ? void 0 : name.val))
175
+ continue;
176
+ const body = ((_e = (_d = part.docPartBody) === null || _d === void 0 ? void 0 : _d.content) !== null && _e !== void 0 ? _e : []);
177
+ const paragraphs = body.filter((e) => e.value.TYPE_NAME === 'org_docx4j_wml.P');
178
+ out.set(name.val, { text: paragraphs.map((p) => textOf(p.value)).join('\r'), body });
179
+ }
180
+ return out;
181
+ }
182
+ /**
183
+ * What a control shows of an entry (CR-002 section 63.18, as revised by 63.21): a copy of each of its body's paragraphs'
184
+ * run-level content, every run as the glossary holds it. Word brought an entry back so, the control's own run properties
185
+ * not over it (check 68, cases 21 to 24), and doesn't restyle it (check 65's 65b, case 9). The paragraphs' properties stay
186
+ * in the glossary, whose styles they name.
187
+ */
188
+ export function entryContentOf(body) {
189
+ var _a;
190
+ const out = [];
191
+ for (const block of body) {
192
+ if (block.value.TYPE_NAME !== 'org_docx4j_wml.P')
193
+ continue;
194
+ const items = ((_a = block.value.content) !== null && _a !== void 0 ? _a : []).filter((e) => { var _a, _b; return !['proofErr', 'bookmarkStart', 'bookmarkEnd'].includes((_b = (_a = e.name) === null || _a === void 0 ? void 0 : _a.localPart) !== null && _b !== void 0 ? _b : ''); });
195
+ out.push(items.map((item) => ({ name: item.name, value: deepCopy(item.value) })));
196
+ }
197
+ return out;
198
+ }
199
+ /**
200
+ * A placeholder entry as Word writes one (check 65): the default entry's `w:docPartPr` (category General, gallery
201
+ * placeholder, type `bbPlcHdr`, behaviour content) with a `w:guid` of its own, and a body of one paragraph, one run in
202
+ * the Placeholder Text style.
203
+ */
204
+ function docPartOf(name, text) {
205
+ const pr = f.createCTDocPartPr({
206
+ nameOrStyleOrCategory: [
207
+ f.createCTDocPartName({ val: name }),
208
+ f.createCTDocPartCategory({ name: f.createCTDocPartCategoryName({ val: 'General' }), gallery: f.createCTDocPartGallery({ val: 'placeholder' }) }),
209
+ f.createCTDocPartTypes({ type: [f.createCTDocPartType({ val: 'bbPlcHdr' })] }),
210
+ f.createCTDocPartBehaviors({ behavior: [f.createCTDocPartBehavior({ val: 'content' })] }),
211
+ f.createCTGuid({ val: `{${uuid().toUpperCase()}}` }),
212
+ ],
213
+ });
214
+ const body = f.createBody({ content: [el.p({ content: [placeholderRun(undefined, text)] })] });
215
+ return f.createCTDocPart({ docPartPr: pr, docPartBody: body });
216
+ }
217
+ /** Default Paragraph Font as Word defines it, which Placeholder Text is based on (check 34's glossary). */
218
+ function defaultParagraphFont() {
219
+ return f.createStyle({
220
+ type: 'character', _default: true, styleId: 'DefaultParagraphFont',
221
+ name: f.createStyleName({ val: 'Default Paragraph Font' }),
222
+ uiPriority: f.createStyleUiPriority({ val: 1 }),
223
+ semiHidden: f.createBooleanDefaultTrue(),
224
+ unhideWhenUsed: f.createBooleanDefaultTrue(),
225
+ });
226
+ }
227
+ /** The glossary's styles part holds Placeholder Text and the style it is based on, made where the glossary has none. */
228
+ async function ensureGlossaryStyles(glossary, make) {
229
+ var _a, _b;
230
+ const holder = glossary;
231
+ let part = holder.styleDefinitionsPart;
232
+ if (!part) {
233
+ part = make();
234
+ part.setContents(f.createStyles({ style: [] }));
235
+ holder.addTargetPart(part);
236
+ }
237
+ else {
238
+ // a styles part that has them already stays untouched
239
+ const read = await part.readContents().catch(() => undefined);
240
+ const ids = new Set(((_a = read === null || read === void 0 ? void 0 : read.style) !== null && _a !== void 0 ? _a : []).map((s) => s.styleId));
241
+ if (ids.has('DefaultParagraphFont') && ids.has(PLACEHOLDER_STYLE))
242
+ return;
243
+ }
244
+ const styles = await part.getContents();
245
+ const list = ((_b = styles.style) !== null && _b !== void 0 ? _b : (styles.style = []));
246
+ const ids = new Set(list.map((s) => s.styleId));
247
+ for (const style of [defaultParagraphFont(), placeholderStyle()]) {
248
+ if (ids.has(style.styleId))
249
+ continue;
250
+ list.push(style);
251
+ linkParents(style, styles);
252
+ }
253
+ }