@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
@@ -1,7 +1,16 @@
1
1
  // src/docx/session.ts
2
- import { parseNumbering } from "../numbering/parseNumbering.js";
2
+ import {
3
+ parseNumbering
4
+ } from "../numbering/parseNumbering.js";
5
+ var sessionsOpened = 0;
6
+ function newSessionId() {
7
+ sessionsOpened += 1;
8
+ return `d${sessionsOpened}-${Math.random().toString(36).slice(2, 10)}`;
9
+ }
3
10
  var SessionStore = class {
4
11
  kind = "docxSession";
12
+ /** Which open document this is. Every block key of it names it, so a block of another document is not taken for one of this one */
13
+ sessionId;
5
14
  parts;
6
15
  mainPartPath;
7
16
  documentPrefix;
@@ -12,19 +21,12 @@ var SessionStore = class {
12
21
  defaults;
13
22
  /** The effective automatic tab interval read from settings.xml. Used for display only. */
14
23
  defaultTabStopPt;
15
- /** The paragraph properties at the base of the OOXML formatting hierarchy. */
16
- paragraphDefaults;
17
24
  /** The paper this document is written on, read from the first section. Used for display only: the `w:sectPr` itself goes back out in the preserved tail */
18
25
  geometry;
19
- /** The style chain from styles.xml. Used only to fold the style a paragraph points at into its displayed values */
20
- styles;
26
+ /** Everything the display values of a paragraph or a run are resolved against: the style chain, the defaults, the list definitions */
27
+ formatting;
21
28
  /** The paragraph styles this document defines, in the order styles.xml lists them. What the style picker offers */
22
29
  paragraphStyles;
23
- /**
24
- * The paragraph style every paragraph with no `w:pStyle` of its own wears (`w:default="1"`,
25
- * usually Normal). null when the document marks none
26
- */
27
- defaultParagraphStyleId;
28
30
  /** The raw text of numbering.xml. Kept around so list numbers can be drawn on screen. null if there is none */
29
31
  numberingXml;
30
32
  /** Where numbering.xml sits inside the zip. When a new list is exported it is rewritten at that spot. null if there is none */
@@ -36,6 +38,7 @@ var SessionStore = class {
36
38
  /** First-section header and footer stories projected for the page preview. */
37
39
  headersFooters;
38
40
  constructor(opened) {
41
+ this.sessionId = opened.sessionId;
39
42
  this.parts = opened.parts;
40
43
  this.mainPartPath = opened.mainPartPath;
41
44
  this.documentPrefix = opened.documentPrefix;
@@ -44,11 +47,9 @@ var SessionStore = class {
44
47
  this.blocks = opened.blocks;
45
48
  this.defaults = opened.defaults;
46
49
  this.defaultTabStopPt = opened.defaultTabStopPt;
47
- this.paragraphDefaults = opened.paragraphDefaults;
48
50
  this.geometry = opened.geometry;
49
- this.styles = opened.styles;
51
+ this.formatting = opened.formatting;
50
52
  this.paragraphStyles = opened.paragraphStyles;
51
- this.defaultParagraphStyleId = opened.defaultParagraphStyleId;
52
53
  this.numberingXml = opened.numberingXml;
53
54
  this.numberingPartPath = opened.numberingPartPath;
54
55
  this.comments = opened.comments;
@@ -63,17 +64,42 @@ function sessionOf(session) {
63
64
  function documentPartPath(session) {
64
65
  return sessionOf(session).mainPartPath;
65
66
  }
66
- function documentNumbering(session) {
67
- return parseNumbering(sessionOf(session).numberingXml);
67
+ function documentNumbering(session, options) {
68
+ return parseNumbering(sessionOf(session).numberingXml, options);
69
+ }
70
+ var BODY_STORY_KEY = "body";
71
+ function blockKey(session, storyKey, index) {
72
+ return `${session.sessionId}:${storyKey}:${index}`;
73
+ }
74
+ function splitBlockKey(key) {
75
+ const first = key.indexOf(":");
76
+ const last = key.lastIndexOf(":");
77
+ if (first < 1 || last <= first + 1) return null;
78
+ const indexText = key.slice(last + 1);
79
+ if (!/^\d+$/.test(indexText)) return null;
80
+ const index = Number(indexText);
81
+ if (!Number.isSafeInteger(index)) return null;
82
+ return {
83
+ sessionId: key.slice(0, first),
84
+ storyKey: key.slice(first + 1, last),
85
+ index
86
+ };
68
87
  }
69
88
  function originalBlock(node, session) {
70
89
  const srcId = node.attrs.srcId;
71
- return typeof srcId === "number" ? session.blocks[srcId] : void 0;
90
+ if (typeof srcId !== "string") return void 0;
91
+ const key = splitBlockKey(srcId);
92
+ if (key === null || key.sessionId !== session.sessionId) return void 0;
93
+ return key.storyKey === BODY_STORY_KEY ? session.blocks[key.index] : void 0;
72
94
  }
73
95
  export {
96
+ BODY_STORY_KEY,
74
97
  SessionStore,
98
+ blockKey,
75
99
  documentNumbering,
76
100
  documentPartPath,
101
+ newSessionId,
77
102
  originalBlock,
78
- sessionOf
103
+ sessionOf,
104
+ splitBlockKey
79
105
  };
@@ -0,0 +1,36 @@
1
+ /**
2
+ * The document story as the parts of the package see it: which blocks the editor models and
3
+ * writes back itself, and the text those blocks go back out as.
4
+ *
5
+ * Two files saying the same thing can spell it differently. A block carries the XML it arrived
6
+ * as on its attrs, so a table Word wrote (`w:w` before `w:type` inside a `w:tcW`) and the same
7
+ * table this editor wrote (the other way round) compare unequal attr for attr, and a comparison
8
+ * over the model would call that a body edit. Writing both stories out through the one writer
9
+ * takes the wording out of the comparison and leaves what the story says, because the writer
10
+ * reaches a fixed point on its own output: opening what it wrote and writing it again gives the
11
+ * same text (the test beside this file holds that over every fixture).
12
+ *
13
+ * What falls out of the comparison is of two kinds, and the test beside this file keeps them
14
+ * apart. One is two spellings of a single thing, which the writer settles on one of; forgiving
15
+ * those is the point, and the set of them is open. The other is content the writer builds afresh
16
+ * instead of reading, a table's grid and a drawing's extent among it, which the comparison cannot
17
+ * report because it can no longer see it. That second set is enumerated and meant to shrink: a
18
+ * case joining it is a preservation defect in the writer, not a detail of the comparison.
19
+ */
20
+ import type { Node as PMNode } from "prosemirror-model";
21
+ import type { SessionStore } from "./session";
22
+ /** A document and the package it was opened from, which is what writing it back out takes */
23
+ export interface Story {
24
+ readonly doc: PMNode;
25
+ readonly session: SessionStore;
26
+ }
27
+ /** Whether this is a block the editor takes apart and writes back itself, rather than one it keeps as it came */
28
+ export declare function isModelledBlock(node: PMNode): boolean;
29
+ /**
30
+ * Every top-level block as the writer puts it out, with the markup that is allowed to have
31
+ * changed taken away first.
32
+ *
33
+ * null where a block cannot be written back at all, which is a story no comparison can be held
34
+ * over: a caller judging two files answers such a story the way it answers a changed one.
35
+ */
36
+ export declare function comparableStory(story: Story, strip: (node: PMNode) => PMNode): readonly string[] | null;
@@ -0,0 +1,23 @@
1
+ // src/docx/storyProjection.ts
2
+ import { DocxExportError } from "../ooxml/errors.js";
3
+ import { NO_EXPORT_REFS } from "./exportRefs.js";
4
+ import { serializeBlock } from "./serializeBlock.js";
5
+ function isModelledBlock(node) {
6
+ return node.type.isInGroup("modelled");
7
+ }
8
+ function comparableStory(story, strip) {
9
+ const blocks = [];
10
+ try {
11
+ story.doc.forEach((block) => {
12
+ blocks.push(serializeBlock(strip(block), story.session, NO_EXPORT_REFS));
13
+ });
14
+ } catch (error) {
15
+ if (error instanceof DocxExportError) return null;
16
+ throw error;
17
+ }
18
+ return blocks;
19
+ }
20
+ export {
21
+ comparableStory,
22
+ isModelledBlock
23
+ };
@@ -1,15 +1,23 @@
1
1
  // src/docx/tableFormatting/editing.ts
2
- import { normalizeHex, wAttr } from "../../ooxml/units.js";
3
- import { childByLocalName, escapeXml, localPart } from "../../ooxml/xml.js";
2
+ import { elementXml, wAttrValue } from "../../ooxml/element.js";
3
+ import { wName } from "../../ooxml/names.js";
4
+ import { setAttr } from "../../ooxml/precedence.js";
4
5
  import {
6
+ childElement,
7
+ editChild,
5
8
  parseProps,
6
9
  parsePropsXml,
7
10
  propsChild,
8
11
  renderProps,
9
- setPropsChild,
10
- TC_PR_ORDER,
11
- TR_PR_ORDER
12
- } from "../propsXml.js";
12
+ setChild,
13
+ withAttrs
14
+ } from "../../ooxml/props.js";
15
+ import {
16
+ ST_EighthPointMeasure,
17
+ ST_TwipsMeasure,
18
+ TWIPS_PER_PT
19
+ } from "../../ooxml/simpleTypes.js";
20
+ import { normalizeHex } from "../../ooxml/units.js";
13
21
  import {
14
22
  NO_BORDER_DEFAULTS,
15
23
  NO_CELL_MARGINS,
@@ -22,18 +30,6 @@ var ALL_CELL_SIDES = [
22
30
  "left",
23
31
  "right"
24
32
  ];
25
- var TC_BORDERS_ORDER = [
26
- "top",
27
- "start",
28
- "left",
29
- "bottom",
30
- "end",
31
- "right",
32
- "insideH",
33
- "insideV",
34
- "tl2br",
35
- "tr2bl"
36
- ];
37
33
  var SIDE_NAMES = {
38
34
  top: ["top"],
39
35
  bottom: ["bottom"],
@@ -41,270 +37,219 @@ var SIDE_NAMES = {
41
37
  right: ["right", "end"]
42
38
  };
43
39
  var PRESET_BORDER_EIGHTHS = 4;
44
- var THEME_COLOR_ATTRS = ["themeColor", "themeTint", "themeShade"];
45
- var THEME_FILL_ATTRS = ["themeFill", "themeFillTint", "themeFillShade"];
46
40
  var EMPTY_TC_PR = { tag: "w:tcPr", attrs: null, children: [] };
47
- function attrsOf(el) {
48
- return el ? Array.from(el.attributes).map((attr) => [attr.name, attr.value]) : [];
49
- }
50
- function withAttr(attrs, name, value, overridden = []) {
51
- const kept = attrs.filter(([attr]) => !overridden.includes(localPart(attr)));
52
- const at = kept.findIndex(([attr]) => localPart(attr) === name);
53
- if (at === -1) return [...kept, [`w:${name}`, value]];
54
- return kept.map(
55
- (attr, index) => index === at ? [attr[0], value] : attr
56
- );
57
- }
58
- function elementXml(tag, attrs) {
59
- const text = attrs.map(([name, value]) => `${name}="${escapeXml(value)}"`).join(" ");
60
- return `<${tag} ${text}/>`;
61
- }
62
41
  function drawsLine(side) {
63
- if (!side) return false;
64
- const val = wAttr(side, "val");
42
+ if (side.tag === null) return false;
43
+ const val = wAttrValue(side.attrs, "val");
65
44
  return val !== null && val !== "nil" && val !== "none";
66
45
  }
67
- function sideAttrs(current, edit, fallback = null) {
46
+ function sideAttrs(name, current, edit, fallback = null) {
68
47
  if (edit.kind === "color") {
69
48
  if (!drawsLine(current)) {
70
- if (current || !fallback || fallback === "none") return null;
49
+ if (current.tag !== null || fallback === null || fallback.val === "none") {
50
+ return null;
51
+ }
71
52
  return inheritedBorderAttrs(fallback, edit.hex);
72
53
  }
73
- return withAttr(
74
- attrsOf(current),
75
- "color",
76
- edit.hex ?? "auto",
77
- THEME_COLOR_ATTRS
78
- );
54
+ return setAttr(current.attrs, name, "color", edit.hex ?? "auto");
79
55
  }
80
56
  if (edit.line === "none") {
81
- return current ? withAttr(attrsOf(current), "val", "none") : [["w:val", "none"]];
57
+ return current.tag !== null ? setAttr(current.attrs, name, "val", "none") : [[wName("val"), "none"]];
82
58
  }
83
- if (!current) {
59
+ if (current.tag === null) {
84
60
  return [
85
- ["w:val", "single"],
86
- ["w:sz", `${PRESET_BORDER_EIGHTHS}`],
87
- ["w:space", "0"],
88
- ["w:color", "auto"]
61
+ [wName("val"), "single"],
62
+ [wName("sz"), `${PRESET_BORDER_EIGHTHS}`],
63
+ [wName("space"), "0"],
64
+ [wName("color"), "auto"]
89
65
  ];
90
66
  }
91
- return withAttr(
92
- withAttr(attrsOf(current), "val", "single"),
67
+ return setAttr(
68
+ setAttr(current.attrs, name, "val", "single"),
69
+ name,
93
70
  "sz",
94
71
  `${PRESET_BORDER_EIGHTHS}`
95
72
  );
96
73
  }
97
- var BORDER_VAL_BY_CSS_STYLE = {
98
- solid: "single",
99
- double: "double",
100
- dashed: "dashed",
101
- dotted: "dotted"
102
- };
103
- function inheritedBorderAttrs(border, hex) {
104
- const matched = border.match(
105
- /^(\d+(?:\.\d+)?)pt (solid|double|dashed|dotted) (#[0-9a-f]{6})$/i
106
- );
107
- if (!matched) return null;
108
- const [, widthText, style, currentColor] = matched;
109
- if (hex !== null && normalizeHex(currentColor) === hex) return null;
110
- const eighths = Math.round(Number.parseFloat(widthText) * 8);
111
- if (!Number.isSafeInteger(eighths) || eighths <= 0) return null;
74
+ function inheritedBorderAttrs(line, hex) {
75
+ const current = line.color.kind === "rgb" ? normalizeHex(line.color.hex) : null;
76
+ if (hex !== null && current === hex) return null;
77
+ const thickness = ST_EighthPointMeasure.format(line.eighths);
78
+ if (thickness === null) return null;
112
79
  return [
113
- ["w:val", BORDER_VAL_BY_CSS_STYLE[style.toLowerCase()]],
114
- ["w:sz", `${eighths}`],
115
- ["w:space", "0"],
116
- ["w:color", hex ?? "auto"]
80
+ [wName("val"), line.val],
81
+ [wName("sz"), thickness],
82
+ [wName("space"), "0"],
83
+ [wName("color"), hex ?? "auto"]
117
84
  ];
118
85
  }
119
86
  function sideNames(borders, side) {
120
87
  const [primary, alternate] = SIDE_NAMES[side];
121
- const usesAlternate = alternate !== void 0 && borders !== null && childByLocalName(borders, primary) === null && childByLocalName(borders, alternate) !== null;
88
+ const usesAlternate = alternate !== void 0 && propsChild(borders.children, primary) === void 0 && propsChild(borders.children, alternate) !== void 0;
122
89
  if (usesAlternate) return { write: alternate, drop: null };
123
90
  return { write: primary, drop: alternate ?? null };
124
91
  }
125
- function editedBorders(current, edit, defaults = NO_BORDER_DEFAULTS) {
126
- const props = current === null ? { tag: "w:tcBorders", attrs: null, children: [] } : parseProps(current);
127
- const borders = current === null ? null : parsePropsXml(current);
128
- if (!props || current !== null && !borders) return null;
92
+ function leafElement(tag, attrs) {
93
+ return withAttrs({ tag, attrs: null, children: [] }, attrs);
94
+ }
95
+ function containerOf(props, name) {
96
+ const current = propsChild(props.children, name)?.xml;
97
+ if (current === void 0) {
98
+ return { tag: wName(name), attrs: null, children: [] };
99
+ }
100
+ return parseProps(current);
101
+ }
102
+ function editedBorders(props, edit, defaults) {
103
+ const borders = containerOf(props, "tcBorders");
104
+ if (!borders) return null;
129
105
  const sides = edit.kind === "line" ? edit.sides : ALL_CELL_SIDES;
130
- const children = sides.reduce((kept, side) => {
106
+ return sides.reduce((kept, side) => {
107
+ if (kept === null) return null;
131
108
  const { write, drop } = sideNames(borders, side);
132
- const currentSide = borders ? childByLocalName(borders, write) : null;
133
- const attrs = sideAttrs(currentSide, edit, defaults[side]);
109
+ const currentSide = childElement(borders, write);
110
+ if (!currentSide) return null;
111
+ const attrs = sideAttrs(write, currentSide, edit, defaults[side]);
134
112
  if (!attrs) return kept;
135
- const tag = currentSide?.nodeName ?? `w:${write}`;
136
- const written = setPropsChild(
113
+ const written = editChild(
137
114
  kept,
138
- write,
139
- elementXml(tag, attrs),
140
- TC_BORDERS_ORDER
115
+ ["tcBorders", write],
116
+ () => leafElement(currentSide.tag ?? wName(write), attrs)
141
117
  );
142
- return drop === null ? written : setPropsChild(written, drop, null, TC_BORDERS_ORDER);
143
- }, props.children);
144
- const xml = renderProps({ ...props, children });
145
- return { xml: xml === "" ? null : xml };
118
+ if (written === null || drop === null) return written;
119
+ return editChild(written, ["tcBorders", drop], () => null);
120
+ }, props);
146
121
  }
147
122
  function hasPattern(shd) {
148
- const val = shd ? wAttr(shd, "val") : null;
123
+ const val = wAttrValue(shd.attrs, "val");
149
124
  return val !== null && val !== "clear" && val !== "nil";
150
125
  }
151
126
  function editedShading(current, fill) {
152
- const tag = current?.nodeName ?? "w:shd";
127
+ const tag = current.tag ?? wName("shd");
153
128
  if (fill === null) {
154
129
  if (!hasPattern(current)) return null;
155
- return elementXml(
156
- tag,
157
- withAttr(attrsOf(current), "fill", "auto", THEME_FILL_ATTRS)
158
- );
130
+ return elementXml(tag, setAttr(current.attrs, "shd", "fill", "auto"));
159
131
  }
160
- if (!current) {
132
+ if (current.tag === null) {
161
133
  return elementXml(tag, [
162
- ["w:val", "clear"],
163
- ["w:color", "auto"],
164
- ["w:fill", fill]
134
+ [wName("val"), "clear"],
135
+ [wName("color"), "auto"],
136
+ [wName("fill"), fill]
165
137
  ]);
166
138
  }
167
- const painting = hasPattern(current) ? attrsOf(current) : withAttr(attrsOf(current), "val", "clear");
168
- return elementXml(tag, withAttr(painting, "fill", fill, THEME_FILL_ATTRS));
169
- }
170
- function bordersChange(props, edit, defaults) {
171
- const current = propsChild(props.children, "tcBorders")?.xml ?? null;
172
- const edited = editedBorders(current, edit, defaults);
173
- return edited === null ? null : { name: "tcBorders", xml: edited.xml };
139
+ const painting = hasPattern(current) ? current.attrs : setAttr(current.attrs, "shd", "val", "clear");
140
+ return elementXml(tag, setAttr(painting, "shd", "fill", fill));
174
141
  }
175
- var TC_MAR_ORDER = [
176
- "top",
177
- "start",
178
- "left",
179
- "bottom",
180
- "end",
181
- "right"
182
- ];
183
142
  var MARGIN_SIDE_NAMES = {
184
143
  top: ["top"],
185
144
  right: ["end", "right"],
186
145
  bottom: ["bottom"],
187
146
  left: ["start", "left"]
188
147
  };
189
- function paddingChange(props, values) {
190
- const current = propsChild(props.children, "tcMar")?.xml ?? null;
191
- const margins = current === null ? null : parsePropsXml(current);
192
- const parsed = current === null ? { tag: "w:tcMar", attrs: null, children: [] } : parseProps(current);
193
- if (!parsed || current !== null && !margins) return null;
194
- let children = parsed.children;
148
+ function editedPadding(props, values) {
149
+ const margins = containerOf(props, "tcMar");
150
+ if (!margins) return null;
151
+ let edited = props;
195
152
  let wrote = false;
196
153
  for (const side of ALL_CELL_SIDES) {
197
154
  const points = values[side];
198
155
  if (points === void 0) continue;
199
- const twips = Math.round(points * 20);
200
- if (!Number.isFinite(points) || points < 0 || !Number.isSafeInteger(twips)) {
201
- return null;
202
- }
203
- const existing = margins ? MARGIN_SIDE_NAMES[side].map((name2) => childByLocalName(margins, name2)).find((element) => element !== null) ?? null : null;
204
- const name = existing?.localName ?? side;
205
- const attrs = withAttr(
206
- withAttr(attrsOf(existing), "w", `${twips}`),
156
+ if (points < 0) return null;
157
+ const twips = ST_TwipsMeasure.format(Math.round(points * TWIPS_PER_PT));
158
+ if (twips === null) return null;
159
+ const name = MARGIN_SIDE_NAMES[side].find(
160
+ (spelling) => propsChild(margins.children, spelling) !== void 0
161
+ ) ?? side;
162
+ const existing = childElement(margins, name);
163
+ if (existing === null || edited === null) return null;
164
+ const attrs = setAttr(
165
+ setAttr(existing.attrs, name, "w", twips),
166
+ name,
207
167
  "type",
208
168
  "dxa"
209
169
  );
210
- children = setPropsChild(
211
- children,
212
- name,
213
- elementXml(existing?.nodeName ?? `w:${side}`, attrs),
214
- TC_MAR_ORDER
170
+ edited = editChild(
171
+ edited,
172
+ ["tcMar", name],
173
+ () => leafElement(existing.tag ?? wName(side), attrs)
215
174
  );
216
175
  wrote = true;
217
176
  }
218
- if (!wrote) return null;
219
- return { name: "tcMar", xml: renderProps({ ...parsed, children }) };
177
+ return wrote ? edited : null;
220
178
  }
221
- function childChange(edit, props, defaults) {
179
+ function editedProps(edit, props, defaults) {
222
180
  switch (edit.kind) {
223
181
  case "background": {
224
182
  const fill = edit.hex === null ? null : normalizeHex(edit.hex);
225
183
  if (edit.hex !== null && fill === null) return null;
226
- const current = propsChild(props.children, "shd")?.xml ?? null;
227
- const shd = current === null ? null : parsePropsXml(current);
228
- if (current !== null && !shd) return null;
229
- return { name: "shd", xml: editedShading(shd, fill) };
184
+ const shd = childElement(props, "shd");
185
+ if (!shd) return null;
186
+ return setChild(props, "shd", editedShading(shd, fill));
230
187
  }
231
188
  case "borders":
232
- return bordersChange(
189
+ return editedBorders(
233
190
  props,
234
- {
235
- kind: "line",
236
- line: edit.line,
237
- sides: edit.sides
238
- },
191
+ { kind: "line", line: edit.line, sides: edit.sides },
239
192
  defaults
240
193
  );
241
194
  case "borderColor": {
242
195
  const hex = edit.hex === null ? null : normalizeHex(edit.hex);
243
196
  if (edit.hex !== null && hex === null) return null;
244
- return bordersChange(props, { kind: "color", hex }, defaults);
197
+ return editedBorders(props, { kind: "color", hex }, defaults);
245
198
  }
246
199
  case "verticalAlign": {
247
- const current = propsChild(props.children, "vAlign")?.xml ?? null;
248
- const element = current === null ? null : parsePropsXml(current);
249
- if (current !== null && !element) return null;
250
- return {
251
- name: "vAlign",
252
- xml: elementXml(
253
- element?.nodeName ?? "w:vAlign",
254
- withAttr(attrsOf(element), "val", edit.align)
200
+ const current = childElement(props, "vAlign");
201
+ if (!current) return null;
202
+ return setChild(
203
+ props,
204
+ "vAlign",
205
+ elementXml(
206
+ current.tag ?? wName("vAlign"),
207
+ setAttr(current.attrs, "vAlign", "val", edit.align)
255
208
  )
256
- };
209
+ );
257
210
  }
258
211
  case "padding":
259
- return paddingChange(props, edit.values);
212
+ return editedPadding(props, edit.values);
260
213
  }
261
214
  }
262
215
  function editCellProps(tcPr, edit, defaults = NO_BORDER_DEFAULTS, margins = NO_CELL_MARGINS) {
263
216
  const props = tcPr === null ? EMPTY_TC_PR : parseProps(tcPr);
264
217
  if (!props) return null;
265
- const change = childChange(edit, props, defaults);
266
- if (!change) return null;
267
- const children = setPropsChild(
268
- props.children,
269
- change.name,
270
- change.xml,
271
- TC_PR_ORDER
272
- );
273
- const rendered = renderProps({ ...props, children });
218
+ const edited = editedProps(edit, props, defaults);
219
+ if (!edited) return null;
220
+ const rendered = renderProps(edited);
274
221
  const next = rendered === "" ? null : rendered;
275
222
  if (next === tcPr) return null;
276
223
  return { tcPr: next, format: readCellProps(next, defaults, margins) };
277
224
  }
278
225
  function drawsOwnCellBorder(tcPr) {
279
- const el = tcPr === null ? null : parsePropsXml(tcPr);
280
- const borders = el ? childByLocalName(el, "tcBorders") : null;
226
+ const props = tcPr === null ? null : parseProps(tcPr);
227
+ const borders = props && containerOf(props, "tcBorders");
281
228
  if (!borders) return false;
282
229
  return ALL_CELL_SIDES.some(
283
- (side) => SIDE_NAMES[side].some((name) => drawsLine(childByLocalName(borders, name)))
230
+ (side) => SIDE_NAMES[side].some((name) => {
231
+ const element = childElement(borders, name);
232
+ return element !== null && drawsLine(element);
233
+ })
284
234
  );
285
235
  }
286
236
  function editRowHeight(trPr, heightPt) {
287
- const twips = Math.round(heightPt * 20);
288
- if (!Number.isFinite(heightPt) || heightPt <= 0 || !Number.isSafeInteger(twips)) {
289
- return null;
290
- }
291
- const props = trPr === null ? { tag: "w:trPr", attrs: null, children: [] } : parseProps(trPr);
237
+ const twips = heightPt > 0 ? ST_TwipsMeasure.format(Math.round(heightPt * TWIPS_PER_PT)) : null;
238
+ if (twips === null) return null;
239
+ const props = trPr === null ? { tag: wName("trPr"), attrs: null, children: [] } : parseProps(trPr);
292
240
  if (!props) return null;
293
- const current = propsChild(props.children, "trHeight")?.xml ?? null;
294
- const element = current === null ? null : parsePropsXml(current);
295
- if (current !== null && !element) return null;
296
- const writtenRule = element ? wAttr(element, "hRule") : null;
297
- const attrs = withAttr(attrsOf(element), "val", `${twips}`);
298
- const nextAttrs = withAttr(
241
+ const current = childElement(props, "trHeight");
242
+ if (!current) return null;
243
+ const writtenRule = wAttrValue(current.attrs, "hRule");
244
+ const attrs = setAttr(current.attrs, "trHeight", "val", twips);
245
+ const nextAttrs = setAttr(
299
246
  attrs,
247
+ "trHeight",
300
248
  "hRule",
301
249
  writtenRule === "exact" ? "exact" : "atLeast"
302
250
  );
303
- const child = elementXml(element?.nodeName ?? "w:trHeight", nextAttrs);
304
- const rendered = renderProps({
305
- ...props,
306
- children: setPropsChild(props.children, "trHeight", child, TR_PR_ORDER)
307
- });
251
+ const child = elementXml(current.tag ?? wName("trHeight"), nextAttrs);
252
+ const rendered = renderProps(setChild(props, "trHeight", child));
308
253
  if (rendered === trPr) return null;
309
254
  const parsed = parsePropsXml(rendered);
310
255
  const format = parsed ? readRowFormat(parsed) : null;
@@ -3,14 +3,19 @@
3
3
  * collapsed-border resolution matches OOXML inheritance without changing exported XML.
4
4
  */
5
5
  import type { CellFormat, CellMargins, InsideBorders, RowFormat, TableFormat, TableWidth } from "../../model/format";
6
+ import { type BorderLine } from "../../ooxml/units";
6
7
  export type { CellMargins, InsideBorders } from "../../model/format";
8
+ /** The parts of a table a table style formats conditionally (`w:tblStylePr/@w:type`, ST_TblStyleOverrideType) */
9
+ export type TableStyleOverrideType = "wholeTable" | "firstRow" | "lastRow" | "firstCol" | "lastCol" | "band1Vert" | "band2Vert" | "band1Horz" | "band2Horz" | "neCell" | "nwCell" | "seCell" | "swCell";
7
10
  /**
8
11
  * The width written down by `<w:tblW>` or `<w:tcW>`.
9
12
  * If we cannot make out what it means it is null, and such a width goes back out unchanged on export.
10
13
  *
11
- * `auto` and `nil` leave the width to the layout, so whatever number stands beside them says nothing.
14
+ * `auto` and `nil` leave the width to the layout unless the value carries an explicit percentage.
12
15
  * A percentage may be written in fiftieths of a percent, as in `w:w="2500"`, or as `w:w="50%"`.
13
16
  * Both have to be gathered into the same unit, or the table collapses into a thin strip on screen.
17
+ * Word lets an explicit `%` override `w:type` (MS-OI29500 §2.1.185(b)); see
18
+ * spec/notes/simpleTypes.md. A universal measure under `pct` remains unreadable here.
14
19
  */
15
20
  export declare function readTableWidth(parent: Element | null, name: "tblW" | "tcW"): TableWidth | null;
16
21
  /** The sequence of column widths (dxa) set by `<w:tblGrid>`. Revision markup (tblGridChange) is skipped */
@@ -32,12 +37,17 @@ export declare function readCellMarginsOf(parent: Element | null, name: "tblCell
32
37
  * their cell padding from.
33
38
  */
34
39
  export declare function layerCellMargins(style: CellMargins, direct: CellMargins): CellMargins;
35
- /** The lines a cell falls back on, one per side, already resolved for its spot in the grid */
40
+ /**
41
+ * The lines a cell falls back on, one per side, already resolved for its spot in the grid.
42
+ *
43
+ * They are held as the markup records them rather than as CSS, because a cell that takes one of
44
+ * them over as a border of its own writes it back into `w:tcBorders`.
45
+ */
36
46
  export interface CellBorderDefaults {
37
- top: string | null;
38
- bottom: string | null;
39
- left: string | null;
40
- right: string | null;
47
+ top: BorderLine | null;
48
+ bottom: BorderLine | null;
49
+ left: BorderLine | null;
50
+ right: BorderLine | null;
41
51
  }
42
52
  export declare const NO_BORDER_DEFAULTS: CellBorderDefaults;
43
53
  /** Which sides of a cell lie on the edge of the table's grid */