@portone/docx-editor 0.2.0 → 0.3.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 (256) hide show
  1. package/CHANGELOG.md +174 -0
  2. package/CONTRIBUTING.md +9 -1
  3. package/dist/DocxEditor.d.ts +6 -0
  4. package/dist/DocxEditor.js +12 -29
  5. package/dist/core.d.ts +23 -8
  6. package/dist/core.js +9 -4
  7. package/dist/docx/cloning.d.ts +38 -0
  8. package/dist/docx/cloning.js +74 -0
  9. package/dist/docx/commentOnlyChange.d.ts +14 -7
  10. package/dist/docx/commentOnlyChange.js +9 -139
  11. package/dist/docx/comments/constants.d.ts +3 -4
  12. package/dist/docx/comments/constants.js +4 -5
  13. package/dist/docx/comments/grammar.d.ts +86 -0
  14. package/dist/docx/comments/grammar.js +223 -0
  15. package/dist/docx/comments/parts.d.ts +52 -0
  16. package/dist/docx/comments/parts.js +279 -0
  17. package/dist/docx/comments/people.d.ts +11 -2
  18. package/dist/docx/comments/people.js +28 -106
  19. package/dist/docx/comments/policy.d.ts +7 -0
  20. package/dist/docx/comments/policy.js +43 -0
  21. package/dist/docx/comments/reading.d.ts +5 -3
  22. package/dist/docx/comments/reading.js +30 -29
  23. package/dist/docx/comments/writing.d.ts +19 -7
  24. package/dist/docx/comments/writing.js +105 -137
  25. package/dist/docx/documentSettings.d.ts +7 -0
  26. package/dist/docx/documentSettings.js +10 -2
  27. package/dist/docx/exportDocx.d.ts +40 -5
  28. package/dist/docx/exportDocx.js +71 -72
  29. package/dist/docx/exportRefs.d.ts +5 -2
  30. package/dist/docx/exportRefs.js +3 -1
  31. package/dist/docx/fidelity.d.ts +43 -0
  32. package/dist/docx/fidelity.js +64 -0
  33. package/dist/docx/formatting/attrs.d.ts +27 -0
  34. package/dist/docx/formatting/attrs.js +31 -0
  35. package/dist/docx/formatting/context.d.ts +31 -0
  36. package/dist/docx/formatting/context.js +42 -0
  37. package/dist/docx/formatting/direct.d.ts +6 -5
  38. package/dist/docx/formatting/direct.js +52 -81
  39. package/dist/docx/formatting/resolve.d.ts +45 -0
  40. package/dist/docx/formatting/resolve.js +140 -0
  41. package/dist/docx/formatting/runProperties.d.ts +93 -0
  42. package/dist/docx/formatting/runProperties.js +316 -0
  43. package/dist/docx/formatting/styles.js +3 -3
  44. package/dist/docx/formatting/tabStops.js +7 -22
  45. package/dist/docx/formatting.d.ts +4 -1
  46. package/dist/docx/formatting.js +4 -1
  47. package/dist/docx/headersFooters.js +13 -22
  48. package/dist/docx/identities.d.ts +67 -0
  49. package/dist/docx/identities.js +174 -0
  50. package/dist/docx/importDocx.d.ts +12 -1
  51. package/dist/docx/importDocx.js +72 -79
  52. package/dist/docx/importParagraph.d.ts +1 -1
  53. package/dist/docx/importParagraph.js +4 -3
  54. package/dist/docx/importTable.d.ts +1 -1
  55. package/dist/docx/importTable.js +17 -1
  56. package/dist/docx/invariants.d.ts +33 -0
  57. package/dist/docx/invariants.js +256 -0
  58. package/dist/docx/media.d.ts +6 -4
  59. package/dist/docx/media.js +6 -37
  60. package/dist/docx/newLists.d.ts +20 -0
  61. package/dist/docx/newLists.js +36 -0
  62. package/dist/docx/notes.js +6 -12
  63. package/dist/docx/numberingPlanner.d.ts +8 -0
  64. package/dist/docx/numberingPlanner.js +19 -0
  65. package/dist/docx/packageParts.d.ts +42 -0
  66. package/dist/docx/packageParts.js +135 -0
  67. package/dist/docx/pageGeometry.d.ts +2 -0
  68. package/dist/docx/pageGeometry.js +18 -9
  69. package/dist/docx/paraProps.d.ts +10 -22
  70. package/dist/docx/paraProps.js +75 -99
  71. package/dist/docx/partPlan.d.ts +36 -0
  72. package/dist/docx/partPlan.js +59 -0
  73. package/dist/docx/protectionPolicy.d.ts +127 -0
  74. package/dist/docx/protectionPolicy.js +169 -0
  75. package/dist/docx/relationships.d.ts +1 -1
  76. package/dist/docx/relationships.js +15 -15
  77. package/dist/docx/runProps.d.ts +9 -22
  78. package/dist/docx/runProps.js +15 -168
  79. package/dist/docx/scan.js +20 -52
  80. package/dist/docx/sdt.js +14 -37
  81. package/dist/docx/sdtProps.d.ts +8 -1
  82. package/dist/docx/sdtProps.js +10 -0
  83. package/dist/docx/serializeBlock.d.ts +2 -0
  84. package/dist/docx/serializeBlock.js +9 -7
  85. package/dist/docx/serializeParagraph.d.ts +2 -1
  86. package/dist/docx/serializeParagraph.js +25 -14
  87. package/dist/docx/serializeTable.js +63 -32
  88. package/dist/docx/session.d.ts +42 -14
  89. package/dist/docx/session.js +43 -17
  90. package/dist/docx/storyProjection.d.ts +36 -0
  91. package/dist/docx/storyProjection.js +23 -0
  92. package/dist/docx/tableFormatting/editing.js +130 -185
  93. package/dist/docx/tableFormatting/reading.d.ts +16 -6
  94. package/dist/docx/tableFormatting/reading.js +40 -20
  95. package/dist/docx/tableTemplate.js +37 -5
  96. package/dist/docx/theme.d.ts +0 -6
  97. package/dist/docx/theme.js +0 -8
  98. package/dist/download.d.ts +8 -5
  99. package/dist/download.js +2 -0
  100. package/dist/editor/clipboard/images.js +17 -3
  101. package/dist/editor/clipboard/inlineFormatting.d.ts +7 -0
  102. package/dist/editor/clipboard/inlineFormatting.js +20 -30
  103. package/dist/editor/commands/breakCommands.js +4 -3
  104. package/dist/editor/commands/canRunCommand.d.ts +2 -2
  105. package/dist/editor/commands/canRunCommand.js +1 -1
  106. package/dist/editor/commands/comments/editing.d.ts +6 -2
  107. package/dist/editor/commands/comments/editing.js +21 -36
  108. package/dist/editor/commands/exportQueries.d.ts +15 -0
  109. package/dist/editor/commands/exportQueries.js +14 -0
  110. package/dist/editor/commands/fidelityQueries.d.ts +12 -0
  111. package/dist/editor/commands/fidelityQueries.js +8 -0
  112. package/dist/editor/commands/formatting/editing.d.ts +2 -2
  113. package/dist/editor/commands/formatting/editing.js +26 -108
  114. package/dist/editor/commands/formatting/propertyCommands.d.ts +21 -0
  115. package/dist/editor/commands/formatting/propertyCommands.js +103 -0
  116. package/dist/editor/commands/formatting/shared.d.ts +3 -3
  117. package/dist/editor/commands/formatting/shared.js +5 -2
  118. package/dist/editor/commands/historyCommands.js +2 -1
  119. package/dist/editor/commands/indentCommands.js +5 -4
  120. package/dist/editor/commands/index.d.ts +11 -0
  121. package/dist/editor/commands/index.js +5 -0
  122. package/dist/editor/commands/linkCommands.js +5 -6
  123. package/dist/editor/commands/listCommands.js +8 -10
  124. package/dist/editor/commands/lockCommands.d.ts +7 -1
  125. package/dist/editor/commands/lockCommands.js +35 -37
  126. package/dist/editor/commands/paragraphCommands.js +28 -31
  127. package/dist/editor/commands/spacingCommands.js +1 -1
  128. package/dist/editor/commands/tabCommands.js +4 -3
  129. package/dist/editor/createEditor.d.ts +17 -29
  130. package/dist/editor/createEditor.js +61 -67
  131. package/dist/editor/documentStyles.d.ts +11 -35
  132. package/dist/editor/documentStyles.js +9 -52
  133. package/dist/editor/editorDocument.d.ts +56 -0
  134. package/dist/editor/editorDocument.js +74 -0
  135. package/dist/editor/externalClipboard.js +166 -33
  136. package/dist/editor/insertImage.js +4 -3
  137. package/dist/editor/insertTable.js +4 -3
  138. package/dist/editor/paragraphEdits.d.ts +11 -18
  139. package/dist/editor/paragraphEdits.js +7 -16
  140. package/dist/editor/plugins/displayDerivation.d.ts +47 -0
  141. package/dist/editor/plugins/displayDerivation.js +78 -0
  142. package/dist/editor/plugins/documentProtection.d.ts +3 -3
  143. package/dist/editor/plugins/imagePaste.js +4 -3
  144. package/dist/editor/plugins/keymap.js +18 -3
  145. package/dist/editor/plugins/lockedContent.d.ts +4 -4
  146. package/dist/editor/plugins/lockedContent.js +1 -1
  147. package/dist/editor/plugins/numberingDecorations.d.ts +5 -11
  148. package/dist/editor/plugins/numberingDecorations.js +7 -17
  149. package/dist/editor/plugins/paragraphDisplay.d.ts +7 -0
  150. package/dist/editor/plugins/paragraphDisplay.js +51 -0
  151. package/dist/editor/plugins/tabLayout.js +1 -1
  152. package/dist/editor/plugins/tableDisplay.d.ts +6 -0
  153. package/dist/editor/plugins/tableDisplay.js +16 -0
  154. package/dist/index.d.ts +2 -0
  155. package/dist/model/format.d.ts +30 -8
  156. package/dist/model/format.js +35 -22
  157. package/dist/model/tabStops.d.ts +9 -0
  158. package/dist/model/tabStops.js +18 -0
  159. package/dist/numbering/listTemplate.js +49 -10
  160. package/dist/numbering/parseNumbering.d.ts +14 -1
  161. package/dist/numbering/parseNumbering.js +35 -22
  162. package/dist/numbering/writeNumbering.d.ts +3 -4
  163. package/dist/numbering/writeNumbering.js +14 -25
  164. package/dist/ooxml/childOrder.d.ts +34 -0
  165. package/dist/ooxml/childOrder.js +496 -0
  166. package/dist/ooxml/element.d.ts +65 -0
  167. package/dist/ooxml/element.js +68 -0
  168. package/dist/ooxml/errors.d.ts +3 -2
  169. package/dist/ooxml/fragment.d.ts +53 -0
  170. package/dist/ooxml/fragment.js +76 -0
  171. package/dist/ooxml/image.d.ts +4 -0
  172. package/dist/ooxml/image.js +3 -5
  173. package/dist/ooxml/names.d.ts +27 -0
  174. package/dist/ooxml/names.js +29 -0
  175. package/dist/ooxml/partSplice.d.ts +67 -0
  176. package/dist/ooxml/partSplice.js +169 -0
  177. package/dist/ooxml/precedence.d.ts +31 -0
  178. package/dist/ooxml/precedence.js +51 -0
  179. package/dist/ooxml/props.d.ts +112 -0
  180. package/dist/ooxml/props.js +214 -0
  181. package/dist/ooxml/simpleTypes.d.ts +103 -0
  182. package/dist/ooxml/simpleTypes.js +182 -0
  183. package/dist/ooxml/tabStops.js +8 -27
  184. package/dist/ooxml/tagScan.d.ts +34 -0
  185. package/dist/ooxml/tagScan.js +108 -0
  186. package/dist/ooxml/units.d.ts +25 -13
  187. package/dist/ooxml/units.js +65 -31
  188. package/dist/ooxml/xml.d.ts +52 -0
  189. package/dist/ooxml/xml.js +64 -5
  190. package/dist/page/blockKinds.d.ts +103 -0
  191. package/dist/page/blockKinds.js +11 -0
  192. package/dist/page/kinds/index.d.ts +6 -0
  193. package/dist/page/kinds/index.js +10 -0
  194. package/dist/page/kinds/paragraphKind.d.ts +10 -0
  195. package/dist/page/kinds/paragraphKind.js +71 -0
  196. package/dist/page/kinds/tableKind.d.ts +10 -0
  197. package/dist/page/kinds/tableKind.js +178 -0
  198. package/dist/page/measureBlocks.d.ts +3 -1
  199. package/dist/page/measureBlocks.js +24 -25
  200. package/dist/page/pageDecorations.d.ts +27 -28
  201. package/dist/page/pageDecorations.js +70 -151
  202. package/dist/page/pageLayout.d.ts +10 -62
  203. package/dist/page/pageLayout.js +60 -53
  204. package/dist/page/usePageLayout.d.ts +2 -2
  205. package/dist/page/usePageLayout.js +3 -11
  206. package/dist/schema/attrRoles.d.ts +42 -0
  207. package/dist/schema/attrRoles.js +141 -0
  208. package/dist/schema/displayDerivation.d.ts +82 -0
  209. package/dist/schema/displayDerivation.js +130 -0
  210. package/dist/schema/docxSchema.d.ts +9 -0
  211. package/dist/schema/docxSchema.js +237 -114
  212. package/dist/schema/editGuard.d.ts +99 -0
  213. package/dist/schema/editGuard.js +43 -0
  214. package/dist/schema/guards.d.ts +91 -0
  215. package/dist/schema/guards.js +108 -0
  216. package/dist/schema/index.d.ts +2 -0
  217. package/dist/schema/index.js +2 -0
  218. package/dist/schema/locks.d.ts +17 -77
  219. package/dist/schema/locks.js +26 -70
  220. package/dist/schema/preservedGuards.d.ts +50 -0
  221. package/dist/schema/preservedGuards.js +80 -0
  222. package/dist/schema/protection.d.ts +17 -4
  223. package/dist/schema/protection.js +4 -2
  224. package/dist/schema/sourceEquality.d.ts +19 -0
  225. package/dist/schema/sourceEquality.js +39 -0
  226. package/dist/styles/inlineStyle.js +15 -6
  227. package/dist/table/cellFormatting.d.ts +8 -1
  228. package/dist/table/cellFormatting.js +10 -17
  229. package/dist/table/commands.d.ts +2 -2
  230. package/dist/table/commands.js +20 -18
  231. package/dist/table/format.d.ts +3 -0
  232. package/dist/table/format.js +3 -9
  233. package/dist/table/gridBorders.d.ts +14 -16
  234. package/dist/table/gridBorders.js +3 -41
  235. package/dist/table/index.d.ts +2 -2
  236. package/dist/table/merge.d.ts +4 -8
  237. package/dist/table/merge.js +5 -5
  238. package/package.json +10 -3
  239. package/dist/docx/comments/contentTypes.d.ts +0 -7
  240. package/dist/docx/comments/contentTypes.js +0 -38
  241. package/dist/docx/formatting/effectiveParagraph.d.ts +0 -15
  242. package/dist/docx/formatting/effectiveParagraph.js +0 -81
  243. package/dist/docx/propsXml.d.ts +0 -49
  244. package/dist/docx/propsXml.js +0 -302
  245. package/dist/docx/uniqueControls.d.ts +0 -14
  246. package/dist/docx/uniqueControls.js +0 -62
  247. package/dist/editor/plugins/bookmarkProtection.d.ts +0 -3
  248. package/dist/editor/plugins/bookmarkProtection.js +0 -28
  249. package/dist/editor/plugins/commentReservations.d.ts +0 -5
  250. package/dist/editor/plugins/commentReservations.js +0 -26
  251. package/dist/editor/plugins/noteProtection.d.ts +0 -3
  252. package/dist/editor/plugins/noteProtection.js +0 -25
  253. package/dist/editor/plugins/styledParagraphs.d.ts +0 -15
  254. package/dist/editor/plugins/styledParagraphs.js +0 -65
  255. package/dist/page/tableMeasurements.d.ts +0 -10
  256. package/dist/page/tableMeasurements.js +0 -115
@@ -0,0 +1,76 @@
1
+ // src/ooxml/fragment.ts
2
+ import {
3
+ elementChildren,
4
+ namespaceDecls,
5
+ parseXml,
6
+ RESERVED_PREFIXES,
7
+ W_NS
8
+ } from "./xml.js";
9
+ function ELEMENT(...names) {
10
+ return { kind: "element", names };
11
+ }
12
+ var ANY_ELEMENT = { kind: "element", names: "any" };
13
+ var ATTRIBUTES = { kind: "attributes" };
14
+ function loneElement(xml) {
15
+ let root;
16
+ try {
17
+ root = parseXml(`<x ${namespaceDecls(xml)}>${xml}</x>`).documentElement;
18
+ } catch {
19
+ return null;
20
+ }
21
+ if (root.childNodes.length !== 1) return null;
22
+ const el = elementChildren(root)[0];
23
+ return el === void 0 || rebindsReservedPrefix(el) ? null : el;
24
+ }
25
+ function isNamed(el, names) {
26
+ if (!names.includes(el.localName)) return false;
27
+ const declaration = el.prefix === null ? "xmlns" : `xmlns:${el.prefix}`;
28
+ for (let scope = el; scope.parentElement !== null; scope = scope.parentElement) {
29
+ if (scope.hasAttribute(declaration)) return el.namespaceURI === W_NS;
30
+ }
31
+ return true;
32
+ }
33
+ function holdsHeadAlone(el, shape) {
34
+ const closing = loneElement(`<w:${shape.name}>${shape.closedBy}`);
35
+ if (closing === null) return false;
36
+ const children = elementChildren(el);
37
+ const own = children.slice(
38
+ 0,
39
+ children.length - elementChildren(closing).length
40
+ );
41
+ return el.childNodes.length === children.length && own.every((child) => isNamed(child, shape.head));
42
+ }
43
+ function rebindsReservedPrefix(el) {
44
+ return Array.from(el.attributes).some((attr) => {
45
+ if (attr.prefix !== "xmlns") return false;
46
+ const reserved = RESERVED_PREFIXES.get(attr.localName);
47
+ return reserved !== void 0 && reserved !== attr.value;
48
+ }) || elementChildren(el).some(rebindsReservedPrefix);
49
+ }
50
+ function holdsShape(shape, value) {
51
+ switch (shape.kind) {
52
+ case "element": {
53
+ const el = loneElement(value);
54
+ if (el === null) return false;
55
+ return shape.names === "any" || isNamed(el, shape.names);
56
+ }
57
+ case "attributes": {
58
+ const el = loneElement(`<y ${value}></y>`);
59
+ return el !== null && el.childNodes.length === 0;
60
+ }
61
+ case "openTag": {
62
+ const el = loneElement(value + shape.closedBy);
63
+ return el !== null && holdsHeadAlone(el, shape);
64
+ }
65
+ }
66
+ }
67
+ function acceptRawXml(shape, value) {
68
+ if (value === null) return null;
69
+ return holdsShape(shape, value) ? value : false;
70
+ }
71
+ export {
72
+ ANY_ELEMENT,
73
+ ATTRIBUTES,
74
+ ELEMENT,
75
+ acceptRawXml
76
+ };
@@ -10,6 +10,10 @@
10
10
  *
11
11
  * An imported image holds on to its whole original `<w:drawing>` XML, so an untouched
12
12
  * image goes back out byte for byte. A resize rewrites nothing but the two extents.
13
+ *
14
+ * `emuToPx` is imported on its own by a builder working in pixels, so nothing here reads
15
+ * `NAMESPACES` at the top level: a top-level read is an impure statement a bundler keeps,
16
+ * and it would hold the naming layer in a bundle that wanted one multiplication.
13
17
  */
14
18
  /**
15
19
  * English Metric Units, the unit every DrawingML length is written in.
@@ -1,4 +1,5 @@
1
1
  // src/ooxml/image.ts
2
+ import { NAMESPACES } from "./names.js";
2
3
  import { childByLocalName, escapeXml, R_NS } from "./xml.js";
3
4
  var EMU_PER_PX = 9525;
4
5
  function emuToPx(emu) {
@@ -42,7 +43,6 @@ function toImageExtent(value) {
42
43
  const cy = positiveInt(value.cy);
43
44
  return cx === null || cy === null ? null : { cx, cy };
44
45
  }
45
- var PICTURE_URI = "http://schemas.openxmlformats.org/drawingml/2006/picture";
46
46
  function childOf(el, name) {
47
47
  return el ? childByLocalName(el, name) : null;
48
48
  }
@@ -62,7 +62,7 @@ function readDrawingPicture(drawing) {
62
62
  const extent = readExtent(childOf(inline, "extent"));
63
63
  if (!extent) return null;
64
64
  const graphicData = childOf(childOf(inline, "graphic"), "graphicData");
65
- if (!graphicData || graphicData.getAttribute("uri") !== PICTURE_URI) {
65
+ if (!graphicData || graphicData.getAttribute("uri") !== NAMESPACES.pic) {
66
66
  return null;
67
67
  }
68
68
  const blip = childOf(
@@ -82,13 +82,11 @@ function withExtent(xml, extent) {
82
82
  const size = `cx="${extent.cx}" cy="${extent.cy}"`;
83
83
  return xml.replace(EXTENT_TAG, (_match, prefix) => `<${prefix ?? ""}extent ${size}/>`).replace(EXT_TAG, (_match, prefix) => `<${prefix ?? ""}ext ${size}/>`);
84
84
  }
85
- var WP_NS = "http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing";
86
- var A_NS = "http://schemas.openxmlformats.org/drawingml/2006/main";
87
85
  function imageDrawingXml(image) {
88
86
  const size = `cx="${image.extent.cx}" cy="${image.extent.cy}"`;
89
87
  const name = `Picture ${image.docPrId}`;
90
88
  const descr = image.alt === null ? "" : ` descr="${escapeXml(image.alt)}"`;
91
- return `<w:drawing><wp:inline xmlns:wp="${WP_NS}" distT="0" distB="0" distL="0" distR="0"><wp:extent ${size}/><wp:effectExtent l="0" t="0" r="0" b="0"/><wp:docPr id="${image.docPrId}" name="${name}"${descr}/><wp:cNvGraphicFramePr><a:graphicFrameLocks xmlns:a="${A_NS}" noChangeAspect="1"/></wp:cNvGraphicFramePr><a:graphic xmlns:a="${A_NS}"><a:graphicData uri="${PICTURE_URI}"><pic:pic xmlns:pic="${PICTURE_URI}"><pic:nvPicPr><pic:cNvPr id="${image.docPrId}" name="${name}"${descr}/><pic:cNvPicPr/></pic:nvPicPr><pic:blipFill><a:blip xmlns:r="${R_NS}" r:embed="${escapeXml(image.relId)}"/><a:stretch><a:fillRect/></a:stretch></pic:blipFill><pic:spPr><a:xfrm><a:off x="0" y="0"/><a:ext ${size}/></a:xfrm><a:prstGeom prst="rect"><a:avLst/></a:prstGeom></pic:spPr></pic:pic></a:graphicData></a:graphic></wp:inline></w:drawing>`;
89
+ return `<w:drawing><wp:inline xmlns:wp="${NAMESPACES.wp}" distT="0" distB="0" distL="0" distR="0"><wp:extent ${size}/><wp:effectExtent l="0" t="0" r="0" b="0"/><wp:docPr id="${image.docPrId}" name="${name}"${descr}/><wp:cNvGraphicFramePr><a:graphicFrameLocks xmlns:a="${NAMESPACES.a}" noChangeAspect="1"/></wp:cNvGraphicFramePr><a:graphic xmlns:a="${NAMESPACES.a}"><a:graphicData uri="${NAMESPACES.pic}"><pic:pic xmlns:pic="${NAMESPACES.pic}"><pic:nvPicPr><pic:cNvPr id="${image.docPrId}" name="${name}"${descr}/><pic:cNvPicPr/></pic:nvPicPr><pic:blipFill><a:blip xmlns:r="${R_NS}" r:embed="${escapeXml(image.relId)}"/><a:stretch><a:fillRect/></a:stretch></pic:blipFill><pic:spPr><a:xfrm><a:off x="0" y="0"/><a:ext ${size}/></a:xfrm><a:prstGeom prst="rect"><a:avLst/></a:prstGeom></pic:spPr></pic:pic></a:graphicData></a:graphic></wp:inline></w:drawing>`;
92
90
  }
93
91
  export {
94
92
  EMU_PER_PX,
@@ -0,0 +1,27 @@
1
+ /**
2
+ * The namespace prefixes the writer spells out, and the namespace each one stands for.
3
+ *
4
+ * Reading is another matter: a document may bind these prefixes to names of its own, so an element
5
+ * that arrived in the file is looked up by its local part. What this package writes is settled
6
+ * here, and the part a fragment is spliced into declares the same binding, so the prefix is
7
+ * decided once rather than at every call site.
8
+ */
9
+ /** The prefix every WordprocessingML element and attribute this package writes carries */
10
+ export declare const W_PREFIX = "w";
11
+ export declare const NAMESPACES: {
12
+ readonly w: "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
13
+ readonly r: "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
14
+ readonly w14: "http://schemas.microsoft.com/office/word/2010/wordml";
15
+ readonly w15: "http://schemas.microsoft.com/office/word/2012/wordml";
16
+ readonly mc: "http://schemas.openxmlformats.org/markup-compatibility/2006";
17
+ readonly wp: "http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing";
18
+ readonly a: "http://schemas.openxmlformats.org/drawingml/2006/main";
19
+ readonly pic: "http://schemas.openxmlformats.org/drawingml/2006/picture";
20
+ };
21
+ export type KnownPrefix = keyof typeof NAMESPACES;
22
+ /** The name `local` goes out under (`qualify("w15", "commentEx")` -> `w15:commentEx`) */
23
+ export declare function qualify(prefix: KnownPrefix, local: string): string;
24
+ /** `wName("val")` -> `w:val`. The one place the written WordprocessingML prefix is decided */
25
+ export declare function wName(local: string): string;
26
+ /** The declaration a part root carries for one prefix, ready to write inside an opening tag */
27
+ export declare function xmlnsDecl(prefix: KnownPrefix): string;
@@ -0,0 +1,29 @@
1
+ // src/ooxml/names.ts
2
+ import { R_NS, W_NS } from "./xml.js";
3
+ var W_PREFIX = "w";
4
+ var NAMESPACES = {
5
+ w: W_NS,
6
+ r: R_NS,
7
+ w14: "http://schemas.microsoft.com/office/word/2010/wordml",
8
+ w15: "http://schemas.microsoft.com/office/word/2012/wordml",
9
+ mc: "http://schemas.openxmlformats.org/markup-compatibility/2006",
10
+ wp: "http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing",
11
+ a: "http://schemas.openxmlformats.org/drawingml/2006/main",
12
+ pic: "http://schemas.openxmlformats.org/drawingml/2006/picture"
13
+ };
14
+ function qualify(prefix, local) {
15
+ return `${prefix}:${local}`;
16
+ }
17
+ function wName(local) {
18
+ return qualify(W_PREFIX, local);
19
+ }
20
+ function xmlnsDecl(prefix) {
21
+ return `xmlns:${prefix}="${NAMESPACES[prefix]}"`;
22
+ }
23
+ export {
24
+ NAMESPACES,
25
+ W_PREFIX,
26
+ qualify,
27
+ wName,
28
+ xmlnsDecl
29
+ };
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Putting children into the root element of a package part, and leaving every byte around them as
3
+ * the part arrived with.
4
+ *
5
+ * A part is text the writers cut and join, never a tree they build again, so the spots to cut at
6
+ * are found in the text: the root's opening tag past the prolog, and its closing tag counted to
7
+ * by depth. Searching for the closing tag instead would take a `</Relationships>` inside a
8
+ * comment for the end of the part, and miss the root altogether when it is written under a
9
+ * prefix or closes on itself.
10
+ */
11
+ import type { KnownPrefix } from "./names";
12
+ /** One child to put into a part, named by the local part of its tag so the child order can place it */
13
+ export interface PartChild {
14
+ name: string;
15
+ xml: string;
16
+ }
17
+ /**
18
+ * What goes into a part's root. Every field is optional and they compose: the children are
19
+ * replaced first, then the inserts are placed among them, then the prepend and the append go on
20
+ * either end, and last the opening tag is rewritten.
21
+ */
22
+ export interface PartSplice {
23
+ /** The root's local name. Any prefix, a self-closing root, a prolog and comments are all handled */
24
+ root: string;
25
+ /** Children put at the spot `CHILD_ORDER[root]` lays down, ahead of the first child that order puts after them */
26
+ insert?: readonly PartChild[];
27
+ /** Put in as the last children */
28
+ append?: string;
29
+ /** Put in right after the opening tag */
30
+ prepend?: string;
31
+ /** Written in place of every child */
32
+ replaceChildren?: string;
33
+ /**
34
+ * Rewrites the opening tag, handed as it is about to be written: opened when children go into a
35
+ * root that closed on itself, else as it arrived.
36
+ */
37
+ rootTag?: (open: string) => string;
38
+ }
39
+ /**
40
+ * Why the part cannot be spliced around its root, or null when it can. The same refusal
41
+ * `splicePart` throws, so a caller may ask ahead and be answered in the same words.
42
+ */
43
+ export declare function partRootProblem(xml: string, root: string): string | null;
44
+ /**
45
+ * The part with the children of `splice` put into its root, and every byte outside what was put
46
+ * in standing as it arrived. A root that closed on itself is opened to hold them.
47
+ *
48
+ * Throws `malformed-xml` naming the part when the root is not there or never closes.
49
+ */
50
+ export declare function splicePart(xml: string, splice: PartSplice): string;
51
+ /**
52
+ * The prefix the root's name carries, colon included, so a child written beside the ones the part
53
+ * holds is spelled the way the root is. Empty for a root written under the default namespace, and
54
+ * for a part with no root, which the splice that follows refuses.
55
+ */
56
+ export declare function rootPrefixOf(xml: string): string;
57
+ export interface RootDeclarations {
58
+ /** The prefixes the root has to bind, and what to bind each to where it does not yet */
59
+ namespaces: Partial<Record<KnownPrefix, string>>;
60
+ /** The tokens `mc:Ignorable` has to name, added to the ones it names already */
61
+ ignorable?: readonly string[];
62
+ }
63
+ /**
64
+ * The part with its root binding every prefix named and its `mc:Ignorable` naming every token
65
+ * asked for, each declared once: one the root declares already is left as it was written.
66
+ */
67
+ export declare function ensureRootDeclarations(xml: string, declarations: RootDeclarations): string;
@@ -0,0 +1,169 @@
1
+ // src/ooxml/partSplice.ts
2
+ import { childOrderOf } from "./childOrder.js";
3
+ import { attrsText } from "./element.js";
4
+ import { DocxExportError } from "./errors.js";
5
+ import { parseAttrs, readTag, rootTagAt } from "./tagScan.js";
6
+ import { localPart } from "./xml.js";
7
+ function closingTagAt(xml, open) {
8
+ let depth = 1;
9
+ let at = open.end;
10
+ for (; ; ) {
11
+ const lt = xml.indexOf("<", at);
12
+ if (lt === -1) return null;
13
+ const tag = readTag(xml, lt);
14
+ if (tag === null) return null;
15
+ if (tag.kind === "open") depth += 1;
16
+ if (tag.kind === "close") {
17
+ depth -= 1;
18
+ if (depth === 0) return tag.name === open.name ? lt : null;
19
+ }
20
+ at = tag.end;
21
+ }
22
+ }
23
+ function readRoot(xml, root) {
24
+ const openAt = rootTagAt(xml);
25
+ const open = openAt === -1 ? null : readTag(xml, openAt);
26
+ if (open === null || open.kind === "close" || localPart(open.name) !== root) {
27
+ return { problem: `the ${root} part has no ${root} root element` };
28
+ }
29
+ if (open.kind === "empty") return { root: { openAt, open, closeAt: null } };
30
+ const closeAt = closingTagAt(xml, open);
31
+ if (closeAt === null) {
32
+ return { problem: `the ${root} part has no closing ${root} tag` };
33
+ }
34
+ return { root: { openAt, open, closeAt } };
35
+ }
36
+ function partRootProblem(xml, root) {
37
+ const reading = readRoot(xml, root);
38
+ return "problem" in reading ? reading.problem : null;
39
+ }
40
+ function directChildren(inner) {
41
+ const children = [];
42
+ let depth = 0;
43
+ let at = 0;
44
+ for (; ; ) {
45
+ const lt = inner.indexOf("<", at);
46
+ if (lt === -1) return children;
47
+ const tag = readTag(inner, lt);
48
+ if (tag === null) return children;
49
+ if (depth === 0 && (tag.kind === "open" || tag.kind === "empty")) {
50
+ children.push({ name: localPart(tag.name), at: lt });
51
+ }
52
+ if (tag.kind === "open") depth += 1;
53
+ if (tag.kind === "close") depth -= 1;
54
+ at = tag.end;
55
+ }
56
+ }
57
+ function insertionAt(children, name, order, root, end) {
58
+ const target = order.indexOf(name);
59
+ if (target === -1) {
60
+ throw new Error(
61
+ `${name} is not a child the order of ${root} knows; add it to CHILD_ORDER`
62
+ );
63
+ }
64
+ const after = children.find((child) => order.indexOf(child.name) > target);
65
+ return after === void 0 ? end : after.at;
66
+ }
67
+ function withInserted(inner, inserts, root) {
68
+ if (inserts.length === 0) return inner;
69
+ const order = childOrderOf(root);
70
+ const children = directChildren(inner);
71
+ const placed = inserts.map((child) => ({
72
+ at: insertionAt(children, child.name, order, root, inner.length),
73
+ xml: child.xml,
74
+ rank: order.indexOf(child.name)
75
+ })).sort((a, b) => a.at - b.at || a.rank - b.rank);
76
+ let out = "";
77
+ let from = 0;
78
+ for (const { at, xml } of placed) {
79
+ out += inner.slice(from, at) + xml;
80
+ from = at;
81
+ }
82
+ return out + inner.slice(from);
83
+ }
84
+ function splicePart(xml, splice) {
85
+ const reading = readRoot(xml, splice.root);
86
+ if ("problem" in reading) {
87
+ throw new DocxExportError("malformed-xml", reading.problem);
88
+ }
89
+ const { openAt, open, closeAt } = reading.root;
90
+ const inner = closeAt === null ? "" : xml.slice(open.end, closeAt);
91
+ const children = (splice.prepend ?? "") + withInserted(
92
+ splice.replaceChildren ?? inner,
93
+ splice.insert ?? [],
94
+ splice.root
95
+ ) + (splice.append ?? "");
96
+ const openTag = xml.slice(openAt, open.end);
97
+ const opened = closeAt === null && children !== "" ? `${openTag.slice(0, -2)}>` : openTag;
98
+ const head = xml.slice(0, openAt) + (splice.rootTag?.(opened) ?? opened);
99
+ if (closeAt !== null) return head + children + xml.slice(closeAt);
100
+ if (children === "") return head + xml.slice(open.end);
101
+ return `${head}${children}</${open.name}>${xml.slice(open.end)}`;
102
+ }
103
+ function rootNameOf(xml) {
104
+ const at = rootTagAt(xml);
105
+ const open = at === -1 ? null : readTag(xml, at);
106
+ return open === null || open.kind === "close" ? null : open.name;
107
+ }
108
+ function rootPrefixOf(xml) {
109
+ const name = rootNameOf(xml) ?? "";
110
+ const colon = name.indexOf(":");
111
+ return colon === -1 ? "" : name.slice(0, colon + 1);
112
+ }
113
+ var IGNORABLE = /\smc:Ignorable\s*=\s*(["'])([^"']*)\1/;
114
+ function withDeclarations(openTag, { namespaces, ignorable }) {
115
+ const tag = readTag(openTag, 0);
116
+ const closeLength = tag?.kind === "empty" ? 2 : 1;
117
+ const attrs = tag === null ? null : parseAttrs(openTag.slice(tag.nameEnd, tag.end - closeLength));
118
+ if (tag === null || attrs === null) {
119
+ throw new DocxExportError(
120
+ "malformed-xml",
121
+ "the root element's attributes could not be read"
122
+ );
123
+ }
124
+ const declared = new Set(attrs.map(([name]) => name));
125
+ const additions = [];
126
+ for (const [prefix, namespace] of Object.entries(namespaces)) {
127
+ if (namespace !== void 0 && !declared.has(`xmlns:${prefix}`)) {
128
+ additions.push([`xmlns:${prefix}`, namespace]);
129
+ }
130
+ }
131
+ const ignoring = IGNORABLE.exec(openTag);
132
+ let tokens = null;
133
+ if (ignorable !== void 0 && ignorable.length > 0) {
134
+ if (ignoring === null) {
135
+ additions.push(["mc:Ignorable", ignorable.join(" ")]);
136
+ } else {
137
+ const had = (attrs.find(([name]) => name === "mc:Ignorable")?.[1] ?? "").split(/\s+/).filter(Boolean);
138
+ const missing = ignorable.filter((token) => !had.includes(token));
139
+ if (missing.length > 0) tokens = [...had, ...missing];
140
+ }
141
+ }
142
+ let next = openTag;
143
+ if (ignoring !== null && tokens !== null) {
144
+ const rewritten = ` mc:Ignorable=${ignoring[1]}${tokens.join(" ")}${ignoring[1]}`;
145
+ next = next.slice(0, ignoring.index) + rewritten + next.slice(ignoring.index + ignoring[0].length);
146
+ }
147
+ if (additions.length === 0) return next;
148
+ const end = next.length - closeLength;
149
+ return `${next.slice(0, end)} ${attrsText(additions)}${next.slice(end)}`;
150
+ }
151
+ function ensureRootDeclarations(xml, declarations) {
152
+ const root = rootNameOf(xml);
153
+ if (root === null) {
154
+ throw new DocxExportError(
155
+ "malformed-xml",
156
+ "the part has no root element to declare a namespace on"
157
+ );
158
+ }
159
+ return splicePart(xml, {
160
+ root: localPart(root),
161
+ rootTag: (open) => withDeclarations(open, declarations)
162
+ });
163
+ }
164
+ export {
165
+ ensureRootDeclarations,
166
+ partRootProblem,
167
+ rootPrefixOf,
168
+ splicePart
169
+ };
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Which attribute beats which when an element writes down both.
3
+ *
4
+ * OOXML records the same setting twice over in several places: a font slot beside the theme
5
+ * reference that overrules it, a color beside the theme color, an indent in twips beside the same
6
+ * indent counted in characters. Writing one of a pair while leaving the other standing means
7
+ * writing a value Word will not show, so the one that would win is dropped as the other is written.
8
+ *
9
+ * The table is keyed by the local names of the owning element and the attribute together, because
10
+ * neither is enough on its own: `color` is an attribute of `w:shd` but an element of its own whose
11
+ * value sits in `w:val`, and `left` is an attribute of `w:ind` but an element of `w:tcMar`.
12
+ */
13
+ import { type XmlAttr } from "./element";
14
+ /**
15
+ * The attributes that override the named one when both are there, so writing the named one drops
16
+ * them (§17.3.1.12 ind, §17.3.2.26 rFonts, §17.3.2.6 color, §17.3.5 shd, §17.3.1.33 spacing).
17
+ *
18
+ * The border sides are the children of `w:tcBorders`, each of them a CT_Border that records a
19
+ * color of its own.
20
+ */
21
+ export declare const OVERRIDING_ATTRS: Readonly<Record<`${string}/${string}`, readonly string[]>>;
22
+ /** What writing `element`'s `name` drops along with it. Empty for a pair nothing overrides */
23
+ export declare function overridingAttrs(element: string, name: string): readonly string[];
24
+ /**
25
+ * `withAttr` with the attributes the new value would be fighting with dropped as it is written.
26
+ *
27
+ * This is how an attribute is meant to be written, so that a writer added later cannot forget the
28
+ * drop. A caller that means to leave an overriding attribute standing says so by reaching for
29
+ * `withAttr` instead.
30
+ */
31
+ export declare function setAttr(attrs: readonly XmlAttr[], element: string, name: string, value: string | null): XmlAttr[];
@@ -0,0 +1,51 @@
1
+ // src/ooxml/precedence.ts
2
+ import { withAttr, withoutAttrs } from "./element.js";
3
+ var THEME_COLOR = [
4
+ "themeColor",
5
+ "themeTint",
6
+ "themeShade"
7
+ ];
8
+ var NO_OVERRIDES = [];
9
+ var OVERRIDING_ATTRS = {
10
+ "rFonts/ascii": ["asciiTheme"],
11
+ "rFonts/hAnsi": ["hAnsiTheme"],
12
+ "rFonts/eastAsia": ["eastAsiaTheme"],
13
+ // This is the slot whose capitalization varies from document to document
14
+ "rFonts/cs": ["cstheme", "csTheme"],
15
+ "color/val": THEME_COLOR,
16
+ "shd/color": THEME_COLOR,
17
+ "shd/fill": ["themeFill", "themeFillTint", "themeFillShade"],
18
+ "ind/left": ["leftChars"],
19
+ "ind/start": ["startChars"],
20
+ "ind/right": ["rightChars"],
21
+ "ind/end": ["endChars"],
22
+ "ind/hanging": ["hangingChars"],
23
+ "ind/firstLine": ["firstLineChars"],
24
+ "spacing/before": ["beforeLines"],
25
+ "spacing/after": ["afterLines"],
26
+ "top/color": THEME_COLOR,
27
+ "start/color": THEME_COLOR,
28
+ "left/color": THEME_COLOR,
29
+ "bottom/color": THEME_COLOR,
30
+ "end/color": THEME_COLOR,
31
+ "right/color": THEME_COLOR,
32
+ "insideH/color": THEME_COLOR,
33
+ "insideV/color": THEME_COLOR,
34
+ "tl2br/color": THEME_COLOR,
35
+ "tr2bl/color": THEME_COLOR
36
+ };
37
+ function overridingAttrs(element, name) {
38
+ return OVERRIDING_ATTRS[`${element}/${name}`] ?? NO_OVERRIDES;
39
+ }
40
+ function setAttr(attrs, element, name, value) {
41
+ return withAttr(
42
+ withoutAttrs(attrs, overridingAttrs(element, name)),
43
+ name,
44
+ value
45
+ );
46
+ }
47
+ export {
48
+ OVERRIDING_ATTRS,
49
+ overridingAttrs,
50
+ setAttr
51
+ };
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Handles the XML fragments that carry formatting, such as `<w:rPr>`, `<w:pPr>`, and `<w:tcPr>`.
3
+ *
4
+ * A fragment is never rebuilt: only the one child a job names is sliced out and swapped, so the
5
+ * rest (borders, shading, margins) stays as the original text wrote it. OOXML lays down the order
6
+ * of the children, so the spot to insert a child that was not there is found by that same order.
7
+ */
8
+ import { type XmlAttr } from "./element";
9
+ export interface PropsChild {
10
+ /** The name with its namespace prefix stripped off (e.g. `gridSpan`) */
11
+ name: string;
12
+ /** This child's original XML fragment exactly as it was */
13
+ xml: string;
14
+ /**
15
+ * Whatever stood between the child before this one and this one: line breaks a producer laid
16
+ * out, comments, anything that is not an element. Carried so that rewriting one child does not
17
+ * quietly drop the rest of what the fragment said. Absent rather than empty when nothing did.
18
+ */
19
+ before?: string;
20
+ }
21
+ export interface Props {
22
+ /** The opening tag name exactly as written (e.g. `w:tcPr`) */
23
+ tag: string;
24
+ attrs: string | null;
25
+ children: PropsChild[];
26
+ /** The same, for what stood between the last child and the closing tag */
27
+ tail?: string;
28
+ }
29
+ /**
30
+ * What stood inside a single element, and "" for one that stood empty or cannot be made out.
31
+ *
32
+ * The opening tag is read rather than scanned for, so a `>` inside an attribute value does not
33
+ * pass for the end of it.
34
+ */
35
+ export declare function innerXml(xml: string): string;
36
+ /**
37
+ * Splits a formatting fragment into its opening tag and its list of children.
38
+ * null if its shape cannot be made out (in which case leaving the original untouched is the safe move).
39
+ */
40
+ export declare function parseProps(xml: string): Props | null;
41
+ /**
42
+ * With nothing to say, the formatting fragment itself is not written. A fragment holding only
43
+ * whitespace says nothing; one holding a comment does, so it is written back.
44
+ */
45
+ export declare function renderProps(props: Props): string;
46
+ /**
47
+ * Replaces a single child with new XML.
48
+ *
49
+ * A null `xml` removes that child. A child that was not there goes into the spot `CHILD_ORDER`
50
+ * lays down for this fragment's own tag, so the caller names what it is writing and not where it
51
+ * goes. Naming the element this fragment stands inside picks the right order where one element
52
+ * carries children in a different order under a different parent (`pPr/rPr`).
53
+ */
54
+ export declare function setChild(props: Props, name: string, xml: string | null, parent?: string): Props;
55
+ export declare function propsChild(children: readonly PropsChild[], name: string): PropsChild | undefined;
56
+ /**
57
+ * The opening tag's attributes as pairs, and null when their shape cannot be made out.
58
+ *
59
+ * The pairs are worked out on demand rather than kept on `Props`, because an untouched fragment
60
+ * has to go back out with the spacing, the quoting and the escaping its producer chose. Only a
61
+ * fragment something writes to passes through `withAttrs`, which is where they are written again.
62
+ */
63
+ export declare function attrsOf(props: Props): XmlAttr[] | null;
64
+ /** The fragment with these attributes written in place of the ones its opening tag carried */
65
+ export declare function withAttrs(props: Props, attrs: readonly XmlAttr[]): Props;
66
+ /**
67
+ * One child of a fragment as it was written: the tag, prefix included, so an edited child keeps
68
+ * the spelling the document chose, and its attributes. A child that is not there has neither, so
69
+ * no tag with attributes hanging off it can be made.
70
+ */
71
+ export type ChildElement = {
72
+ tag: string;
73
+ attrs: readonly XmlAttr[];
74
+ } | {
75
+ tag: null;
76
+ attrs: readonly [];
77
+ };
78
+ /**
79
+ * The tag and attributes of one child. A child that is not there reads as no tag and no
80
+ * attributes, and null says its shape cannot be made out, which leaves the caller to back out
81
+ * rather than write over markup it could not read.
82
+ */
83
+ export declare function childElement(props: Props, name: string): ChildElement | null;
84
+ /**
85
+ * Always writes the element, closing it on its own when it holds nothing. Whitespace alone counts
86
+ * as nothing, the same as `renderProps` reads it, so a pretty-printed fragment emptied of its
87
+ * children collapses rather than keeping the line breaks that stood between them.
88
+ */
89
+ export declare function renderElement(props: Props): string;
90
+ /**
91
+ * An element whose children go in the order `CHILD_ORDER` lays down, whatever order they are
92
+ * handed in. This is how a fragment written from scratch follows the same order an edited one
93
+ * is held to, and a child the order does not know is refused the same way.
94
+ */
95
+ export declare function orderedElement(tag: string, attrs: readonly XmlAttr[], children: readonly PropsChild[]): string;
96
+ /**
97
+ * Edits one child down a path of names, parsing only the fragments the path runs through and
98
+ * leaving every other child as the original text wrote it.
99
+ *
100
+ * The callback is handed the child the path names, null for one that is not there, and answers
101
+ * with what it is to become, null to take it away. A container along the path that was not there
102
+ * is written, and one the edit leaves empty is taken away with it. null when a fragment on the
103
+ * path cannot be made out, which leaves the caller to back out.
104
+ */
105
+ export declare function editChild(props: Props, path: readonly [string, ...string[]], edit: (child: Props | null) => Props | null): Props | null;
106
+ /**
107
+ * Reads a single formatting fragment into an element.
108
+ *
109
+ * A fragment carries no namespace declarations, so they are put back on as it is wrapped.
110
+ * null if its shape cannot be made out, in which case the caller leaves the display values alone.
111
+ */
112
+ export declare function parsePropsXml(xml: string): Element | null;