@portone/docx-editor 0.2.1 → 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 (225) hide show
  1. package/CHANGELOG.md +109 -0
  2. package/CONTRIBUTING.md +5 -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 +12 -7
  10. package/dist/docx/commentOnlyChange.js +9 -156
  11. package/dist/docx/comments/constants.d.ts +0 -1
  12. package/dist/docx/comments/constants.js +0 -2
  13. package/dist/docx/comments/grammar.d.ts +10 -4
  14. package/dist/docx/comments/grammar.js +12 -2
  15. package/dist/docx/comments/{verifying.d.ts → parts.d.ts} +26 -18
  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 +16 -92
  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.js +8 -10
  22. package/dist/docx/comments/writing.d.ts +19 -7
  23. package/dist/docx/comments/writing.js +52 -111
  24. package/dist/docx/documentSettings.d.ts +7 -0
  25. package/dist/docx/documentSettings.js +10 -2
  26. package/dist/docx/exportDocx.d.ts +40 -5
  27. package/dist/docx/exportDocx.js +69 -71
  28. package/dist/docx/exportRefs.d.ts +5 -2
  29. package/dist/docx/exportRefs.js +3 -1
  30. package/dist/docx/fidelity.d.ts +43 -0
  31. package/dist/docx/fidelity.js +64 -0
  32. package/dist/docx/formatting/attrs.d.ts +27 -0
  33. package/dist/docx/formatting/attrs.js +31 -0
  34. package/dist/docx/formatting/context.d.ts +31 -0
  35. package/dist/docx/formatting/context.js +42 -0
  36. package/dist/docx/formatting/direct.d.ts +6 -5
  37. package/dist/docx/formatting/direct.js +52 -78
  38. package/dist/docx/formatting/resolve.d.ts +45 -0
  39. package/dist/docx/formatting/resolve.js +140 -0
  40. package/dist/docx/formatting/runProperties.d.ts +93 -0
  41. package/dist/docx/formatting/runProperties.js +316 -0
  42. package/dist/docx/formatting/styles.js +3 -3
  43. package/dist/docx/formatting/tabStops.js +7 -22
  44. package/dist/docx/formatting.d.ts +4 -1
  45. package/dist/docx/formatting.js +4 -1
  46. package/dist/docx/headersFooters.js +6 -13
  47. package/dist/docx/identities.d.ts +67 -0
  48. package/dist/docx/identities.js +174 -0
  49. package/dist/docx/importDocx.d.ts +12 -1
  50. package/dist/docx/importDocx.js +72 -79
  51. package/dist/docx/importParagraph.d.ts +1 -1
  52. package/dist/docx/importParagraph.js +4 -3
  53. package/dist/docx/importTable.d.ts +1 -1
  54. package/dist/docx/importTable.js +17 -1
  55. package/dist/docx/invariants.d.ts +33 -0
  56. package/dist/docx/invariants.js +256 -0
  57. package/dist/docx/media.d.ts +6 -4
  58. package/dist/docx/media.js +6 -37
  59. package/dist/docx/newLists.d.ts +20 -0
  60. package/dist/docx/newLists.js +36 -0
  61. package/dist/docx/notes.js +3 -7
  62. package/dist/docx/numberingPlanner.d.ts +8 -0
  63. package/dist/docx/numberingPlanner.js +19 -0
  64. package/dist/docx/packageParts.d.ts +42 -0
  65. package/dist/docx/packageParts.js +135 -0
  66. package/dist/docx/pageGeometry.d.ts +2 -0
  67. package/dist/docx/pageGeometry.js +18 -9
  68. package/dist/docx/paraProps.d.ts +10 -22
  69. package/dist/docx/paraProps.js +47 -76
  70. package/dist/docx/partPlan.d.ts +36 -0
  71. package/dist/docx/partPlan.js +59 -0
  72. package/dist/docx/protectionPolicy.d.ts +127 -0
  73. package/dist/docx/protectionPolicy.js +169 -0
  74. package/dist/docx/relationships.d.ts +1 -1
  75. package/dist/docx/relationships.js +8 -13
  76. package/dist/docx/runProps.d.ts +9 -22
  77. package/dist/docx/runProps.js +15 -168
  78. package/dist/docx/scan.js +20 -52
  79. package/dist/docx/sdt.js +9 -34
  80. package/dist/docx/sdtProps.d.ts +8 -1
  81. package/dist/docx/sdtProps.js +10 -0
  82. package/dist/docx/serializeBlock.d.ts +2 -0
  83. package/dist/docx/serializeBlock.js +8 -5
  84. package/dist/docx/serializeTable.js +32 -15
  85. package/dist/docx/session.d.ts +42 -14
  86. package/dist/docx/session.js +43 -17
  87. package/dist/docx/tableFormatting/editing.js +108 -139
  88. package/dist/docx/tableFormatting/reading.d.ts +16 -6
  89. package/dist/docx/tableFormatting/reading.js +40 -20
  90. package/dist/docx/tableTemplate.js +18 -7
  91. package/dist/download.d.ts +8 -5
  92. package/dist/download.js +2 -0
  93. package/dist/editor/clipboard/inlineFormatting.js +19 -30
  94. package/dist/editor/commands/comments/editing.d.ts +6 -2
  95. package/dist/editor/commands/comments/editing.js +19 -31
  96. package/dist/editor/commands/exportQueries.d.ts +15 -0
  97. package/dist/editor/commands/exportQueries.js +14 -0
  98. package/dist/editor/commands/fidelityQueries.d.ts +12 -0
  99. package/dist/editor/commands/fidelityQueries.js +8 -0
  100. package/dist/editor/commands/formatting/editing.d.ts +2 -2
  101. package/dist/editor/commands/formatting/editing.js +26 -108
  102. package/dist/editor/commands/formatting/propertyCommands.d.ts +21 -0
  103. package/dist/editor/commands/formatting/propertyCommands.js +103 -0
  104. package/dist/editor/commands/formatting/shared.d.ts +3 -3
  105. package/dist/editor/commands/formatting/shared.js +5 -2
  106. package/dist/editor/commands/indentCommands.js +5 -4
  107. package/dist/editor/commands/index.d.ts +11 -0
  108. package/dist/editor/commands/index.js +5 -0
  109. package/dist/editor/commands/linkCommands.js +5 -6
  110. package/dist/editor/commands/listCommands.js +8 -10
  111. package/dist/editor/commands/lockCommands.d.ts +7 -1
  112. package/dist/editor/commands/lockCommands.js +35 -46
  113. package/dist/editor/commands/paragraphCommands.js +28 -31
  114. package/dist/editor/commands/spacingCommands.js +1 -1
  115. package/dist/editor/createEditor.d.ts +17 -29
  116. package/dist/editor/createEditor.js +58 -58
  117. package/dist/editor/documentStyles.d.ts +11 -35
  118. package/dist/editor/documentStyles.js +9 -52
  119. package/dist/editor/editorDocument.d.ts +56 -0
  120. package/dist/editor/editorDocument.js +74 -0
  121. package/dist/editor/externalClipboard.js +16 -45
  122. package/dist/editor/insertTable.js +4 -3
  123. package/dist/editor/paragraphEdits.d.ts +11 -18
  124. package/dist/editor/paragraphEdits.js +7 -16
  125. package/dist/editor/plugins/displayDerivation.d.ts +47 -0
  126. package/dist/editor/plugins/displayDerivation.js +78 -0
  127. package/dist/editor/plugins/imagePaste.js +4 -3
  128. package/dist/editor/plugins/keymap.js +18 -3
  129. package/dist/editor/plugins/numberingDecorations.d.ts +5 -11
  130. package/dist/editor/plugins/numberingDecorations.js +7 -17
  131. package/dist/editor/plugins/paragraphDisplay.d.ts +7 -0
  132. package/dist/editor/plugins/paragraphDisplay.js +51 -0
  133. package/dist/editor/plugins/tabLayout.js +1 -1
  134. package/dist/editor/plugins/tableDisplay.d.ts +6 -0
  135. package/dist/editor/plugins/tableDisplay.js +16 -0
  136. package/dist/index.d.ts +2 -0
  137. package/dist/model/format.d.ts +30 -8
  138. package/dist/model/format.js +35 -22
  139. package/dist/model/tabStops.d.ts +9 -0
  140. package/dist/model/tabStops.js +18 -0
  141. package/dist/numbering/listTemplate.js +24 -7
  142. package/dist/numbering/parseNumbering.d.ts +14 -1
  143. package/dist/numbering/parseNumbering.js +35 -22
  144. package/dist/numbering/writeNumbering.d.ts +3 -4
  145. package/dist/numbering/writeNumbering.js +14 -25
  146. package/dist/ooxml/childOrder.d.ts +34 -0
  147. package/dist/ooxml/childOrder.js +496 -0
  148. package/dist/ooxml/element.d.ts +24 -11
  149. package/dist/ooxml/element.js +31 -12
  150. package/dist/ooxml/errors.d.ts +3 -2
  151. package/dist/ooxml/image.d.ts +4 -0
  152. package/dist/ooxml/image.js +2 -5
  153. package/dist/ooxml/partSplice.d.ts +67 -0
  154. package/dist/ooxml/partSplice.js +169 -0
  155. package/dist/ooxml/props.d.ts +112 -0
  156. package/dist/{docx/propsXml.js → ooxml/props.js} +72 -200
  157. package/dist/ooxml/simpleTypes.d.ts +103 -0
  158. package/dist/ooxml/simpleTypes.js +182 -0
  159. package/dist/ooxml/tabStops.js +8 -27
  160. package/dist/ooxml/tagScan.d.ts +34 -0
  161. package/dist/ooxml/tagScan.js +108 -0
  162. package/dist/ooxml/units.d.ts +25 -13
  163. package/dist/ooxml/units.js +65 -31
  164. package/dist/ooxml/xml.d.ts +27 -6
  165. package/dist/ooxml/xml.js +37 -5
  166. package/dist/page/blockKinds.d.ts +61 -0
  167. package/dist/page/blockKinds.js +11 -0
  168. package/dist/page/kinds/index.d.ts +6 -0
  169. package/dist/page/kinds/index.js +10 -0
  170. package/dist/page/kinds/paragraphKind.d.ts +10 -0
  171. package/dist/page/kinds/paragraphKind.js +71 -0
  172. package/dist/page/kinds/tableKind.d.ts +10 -0
  173. package/dist/page/kinds/tableKind.js +178 -0
  174. package/dist/page/measureBlocks.d.ts +3 -14
  175. package/dist/page/measureBlocks.js +24 -45
  176. package/dist/page/pageDecorations.d.ts +27 -44
  177. package/dist/page/pageDecorations.js +70 -152
  178. package/dist/page/pageLayout.d.ts +2 -19
  179. package/dist/page/pageLayout.js +36 -6
  180. package/dist/page/usePageLayout.d.ts +2 -17
  181. package/dist/page/usePageLayout.js +3 -39
  182. package/dist/schema/attrRoles.d.ts +25 -18
  183. package/dist/schema/attrRoles.js +102 -67
  184. package/dist/schema/displayDerivation.d.ts +82 -0
  185. package/dist/schema/displayDerivation.js +130 -0
  186. package/dist/schema/docxSchema.d.ts +3 -0
  187. package/dist/schema/docxSchema.js +26 -14
  188. package/dist/schema/editGuard.d.ts +1 -1
  189. package/dist/schema/guards.d.ts +37 -14
  190. package/dist/schema/guards.js +29 -3
  191. package/dist/schema/index.d.ts +2 -0
  192. package/dist/schema/index.js +2 -0
  193. package/dist/schema/locks.d.ts +11 -44
  194. package/dist/schema/locks.js +0 -9
  195. package/dist/schema/preservedGuards.d.ts +26 -6
  196. package/dist/schema/preservedGuards.js +31 -1
  197. package/dist/schema/protection.d.ts +7 -1
  198. package/dist/schema/protection.js +2 -1
  199. package/dist/schema/sourceEquality.d.ts +1 -9
  200. package/dist/schema/sourceEquality.js +1 -27
  201. package/dist/styles/inlineStyle.js +15 -6
  202. package/dist/table/cellFormatting.d.ts +8 -1
  203. package/dist/table/cellFormatting.js +10 -18
  204. package/dist/table/commands.js +20 -18
  205. package/dist/table/format.d.ts +3 -0
  206. package/dist/table/format.js +3 -9
  207. package/dist/table/gridBorders.d.ts +14 -16
  208. package/dist/table/gridBorders.js +3 -41
  209. package/dist/table/merge.d.ts +2 -6
  210. package/dist/table/merge.js +5 -5
  211. package/package.json +7 -3
  212. package/dist/docx/comments/contentTypes.d.ts +0 -7
  213. package/dist/docx/comments/contentTypes.js +0 -38
  214. package/dist/docx/comments/verifying.js +0 -206
  215. package/dist/docx/formatting/effectiveParagraph.d.ts +0 -15
  216. package/dist/docx/formatting/effectiveParagraph.js +0 -81
  217. package/dist/docx/propsXml.d.ts +0 -67
  218. package/dist/docx/uniqueControls.d.ts +0 -14
  219. package/dist/docx/uniqueControls.js +0 -62
  220. package/dist/editor/plugins/commentReservations.d.ts +0 -5
  221. package/dist/editor/plugins/commentReservations.js +0 -26
  222. package/dist/editor/plugins/styledParagraphs.d.ts +0 -15
  223. package/dist/editor/plugins/styledParagraphs.js +0 -65
  224. package/dist/page/tableMeasurements.d.ts +0 -18
  225. package/dist/page/tableMeasurements.js +0 -119
@@ -0,0 +1,169 @@
1
+ // src/docx/protectionPolicy.ts
2
+ import {
3
+ decodeUtf8,
4
+ elementChildren,
5
+ parseXml,
6
+ withXmlParser
7
+ } from "../ooxml/xml.js";
8
+ import { importDocx } from "./importDocx.js";
9
+ import { CONTENT_TYPES_PATH } from "./packageParts.js";
10
+ import {
11
+ readRelationships,
12
+ relsPathOf
13
+ } from "./relationships.js";
14
+ import { isModelledBlock } from "./storyProjection.js";
15
+ var policies = /* @__PURE__ */ new Map();
16
+ function registerProtectionPolicy(policy) {
17
+ policies.set(policy.level, policy);
18
+ return policy;
19
+ }
20
+ function protectionPolicyFor(level) {
21
+ return policies.get(level);
22
+ }
23
+ function sameBytes(before, after) {
24
+ return before.length === after.length && before.every((byte, index) => byte === after[index]);
25
+ }
26
+ function partPathsOf(policy, session) {
27
+ return policy.parts.map((kind) => kind.pathIn(session));
28
+ }
29
+ function excusedPaths(policy, session) {
30
+ return partPathsOf(policy, session).filter(
31
+ (path) => path !== null
32
+ );
33
+ }
34
+ function aroundTheStory(session) {
35
+ const preserved = session.blocks.filter((block) => !isModelledBlock(block.node)).map((block) => block.xml).join("");
36
+ return session.documentPrefix + preserved + session.documentSuffix;
37
+ }
38
+ function relationshipsKept(before, after, relTypes) {
39
+ if (new Set(before.map((entry) => entry.id)).size !== before.length || new Set(after.map((entry) => entry.id)).size !== after.length) {
40
+ return false;
41
+ }
42
+ const now = new Map(after.map((entry) => [entry.id, entry]));
43
+ const kept = before.every((entry) => {
44
+ const current = now.get(entry.id);
45
+ return current !== void 0 && current.type === entry.type && current.target === entry.target && current.external === entry.external;
46
+ });
47
+ const ids = new Set(before.map((entry) => entry.id));
48
+ const parts = after.filter((entry) => !entry.external);
49
+ return kept && after.filter((entry) => !ids.has(entry.id)).every(
50
+ (entry) => relTypes.includes(entry.type) && !entry.external && parts.filter((other) => other.type === entry.type).length === 1
51
+ );
52
+ }
53
+ function contentTypes(bytes) {
54
+ if (bytes === void 0) return /* @__PURE__ */ new Map();
55
+ const declared = /* @__PURE__ */ new Map();
56
+ for (const el of elementChildren(
57
+ parseXml(decodeUtf8(bytes).text).documentElement
58
+ )) {
59
+ const key = el.localName === "Default" ? el.getAttribute("Extension") : el.localName === "Override" ? el.getAttribute("PartName") : null;
60
+ if (key !== null) declared.set(key, el.getAttribute("ContentType") ?? "");
61
+ }
62
+ return declared;
63
+ }
64
+ function contentTypesKept(before, after, declarable) {
65
+ for (const [key, type] of before) {
66
+ if (after.get(key) !== type) return false;
67
+ }
68
+ for (const [key, type] of after) {
69
+ if (!before.has(key) && !declarable.includes(type)) return false;
70
+ }
71
+ return true;
72
+ }
73
+ function gainedPartsAreNew(policy, before, after) {
74
+ const had = partPathsOf(policy, before);
75
+ return partPathsOf(policy, after).every(
76
+ (path, kind) => path === null || path === had[kind] || !before.parts.has(path)
77
+ );
78
+ }
79
+ function packageKept(policy, before, after) {
80
+ if (before.mainPartPath !== after.mainPartPath) {
81
+ return { ok: false, reason: "part-changed", part: before.mainPartPath };
82
+ }
83
+ const relsPath = relsPathOf(before.mainPartPath);
84
+ const untouched = /* @__PURE__ */ new Set([
85
+ before.mainPartPath,
86
+ relsPath,
87
+ CONTENT_TYPES_PATH,
88
+ ...excusedPaths(policy, before),
89
+ ...excusedPaths(policy, after)
90
+ ]);
91
+ for (const path of /* @__PURE__ */ new Set([...before.parts.keys(), ...after.parts.keys()])) {
92
+ if (untouched.has(path)) continue;
93
+ const was = before.parts.get(path);
94
+ const now = after.parts.get(path);
95
+ if (was === void 0 || now === void 0 || !sameBytes(was, now)) {
96
+ return { ok: false, reason: "part-changed", part: path };
97
+ }
98
+ }
99
+ if (!relationshipsKept(
100
+ readRelationships(before.parts, relsPath),
101
+ readRelationships(after.parts, relsPath),
102
+ policy.parts.map((kind) => kind.relType)
103
+ ) || !gainedPartsAreNew(policy, before, after)) {
104
+ return { ok: false, reason: "relationship-changed", part: relsPath };
105
+ }
106
+ if (!contentTypesKept(
107
+ contentTypes(before.parts.get(CONTENT_TYPES_PATH)),
108
+ contentTypes(after.parts.get(CONTENT_TYPES_PATH)),
109
+ policy.parts.map((kind) => kind.contentType)
110
+ )) {
111
+ return { ok: false, reason: "part-changed", part: CONTENT_TYPES_PATH };
112
+ }
113
+ if (aroundTheStory(before) !== aroundTheStory(after)) {
114
+ return { ok: false, reason: "part-changed", part: before.mainPartPath };
115
+ }
116
+ return { ok: true };
117
+ }
118
+ function partKept(kind, before, after, authorId, options) {
119
+ const arrived = kind.entriesIn(before.session, "arrived") ?? /* @__PURE__ */ new Map();
120
+ const submitted = kind.entriesIn(after.session, "submitted");
121
+ if (submitted === null) return false;
122
+ if (!kind.rootKept(before.session, after.session)) return false;
123
+ const stoodBehindNow = kind.referents(after);
124
+ for (const [id, entry] of submitted) {
125
+ const original = arrived.get(id);
126
+ if (original && original.xml === entry.xml) continue;
127
+ if (!stoodBehindNow.has(id)) return false;
128
+ if (original && kind.anyonesChange(entry.el, original.el)) continue;
129
+ if (!kind.wellFormed(entry.el) || !kind.allowed(
130
+ entry.el,
131
+ original?.el ?? null,
132
+ authorId,
133
+ options,
134
+ after.session
135
+ )) {
136
+ return false;
137
+ }
138
+ }
139
+ const stoodBehindBefore = kind.referents(before);
140
+ return Array.from(arrived.keys()).every(
141
+ (id) => stoodBehindBefore.has(id) || submitted.has(id)
142
+ );
143
+ }
144
+ function partsKept(policy, before, after, authorId, options) {
145
+ for (const kind of policy.parts) {
146
+ const path = kind.pathIn(after.session) ?? kind.pathIn(before.session);
147
+ if (path === null) continue;
148
+ if (!partKept(kind, before, after, authorId, options)) {
149
+ return { ok: false, reason: policy.rejectedMarkup, part: path };
150
+ }
151
+ }
152
+ return { ok: true };
153
+ }
154
+ function verifyChange(policy, original, submitted, authorId, { xmlParser, ...options }) {
155
+ return withXmlParser(xmlParser, () => {
156
+ const before = importDocx(original);
157
+ const after = importDocx(submitted);
158
+ const packaged = packageKept(policy, before.session, after.session);
159
+ if (!packaged.ok) return packaged;
160
+ const story = policy.storyKept(before, after, authorId, options);
161
+ return story.ok ? partsKept(policy, before, after, authorId, options) : story;
162
+ });
163
+ }
164
+ export {
165
+ partsKept,
166
+ protectionPolicyFor,
167
+ registerProtectionPolicy,
168
+ verifyChange
169
+ };
@@ -20,7 +20,7 @@ export interface Relationship {
20
20
  /** Set when the target is a file outside the package */
21
21
  external: boolean;
22
22
  }
23
- export declare function readRelationships(parts: Map<string, Uint8Array>, relsPath: string): Relationship[];
23
+ export declare function readRelationships(parts: ReadonlyMap<string, Uint8Array>, relsPath: string): Relationship[];
24
24
  /** A relationship an export puts in. The target is escaped here, so hand it in as it reads */
25
25
  export interface NewRelationship {
26
26
  type: string;
@@ -1,6 +1,6 @@
1
1
  // src/docx/relationships.ts
2
2
  import { elementXml } from "../ooxml/element.js";
3
- import { DocxExportError } from "../ooxml/errors.js";
3
+ import { rootPrefixOf, splicePart } from "../ooxml/partSplice.js";
4
4
  import {
5
5
  decodeUtf8,
6
6
  elementChildren,
@@ -47,9 +47,9 @@ function takeRelId(taken) {
47
47
  }
48
48
  }
49
49
  }
50
- function relationshipXml(added) {
50
+ function relationshipXml(added, prefix) {
51
51
  const mode = added.external ? [["TargetMode", "External"]] : [];
52
- return elementXml("Relationship", [
52
+ return elementXml(`${prefix}Relationship`, [
53
53
  ["Id", added.id],
54
54
  ["Type", added.type],
55
55
  ["Target", added.target],
@@ -57,23 +57,18 @@ function relationshipXml(added) {
57
57
  ]);
58
58
  }
59
59
  function relationshipsPart(original, added) {
60
- const entries = added.map(relationshipXml).join("");
61
60
  if (!original) {
61
+ const entries2 = added.map((entry) => relationshipXml(entry, "")).join("");
62
62
  return encodeUtf8(
63
- `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Relationships xmlns="${RELATIONSHIPS_NS}">${entries}</Relationships>`,
63
+ `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Relationships xmlns="${RELATIONSHIPS_NS}">${entries2}</Relationships>`,
64
64
  false
65
65
  );
66
66
  }
67
67
  const { text, hadBom } = decodeUtf8(original);
68
- const closing = text.lastIndexOf("</Relationships>");
69
- if (closing === -1) {
70
- throw new DocxExportError(
71
- "malformed-xml",
72
- "the relationships part has no closing tag"
73
- );
74
- }
68
+ const prefix = rootPrefixOf(text);
69
+ const entries = added.map((entry) => relationshipXml(entry, prefix)).join("");
75
70
  return encodeUtf8(
76
- text.slice(0, closing) + entries + text.slice(closing),
71
+ splicePart(text, { root: "Relationships", append: entries }),
77
72
  hadBom
78
73
  );
79
74
  }
@@ -5,30 +5,18 @@
5
5
  * even things we do not know about). So instead of building a new fragment we swap out just the one
6
6
  * child. It is the same principle `tableFormatting` applies to `w:tcPr`.
7
7
  *
8
+ * Which children a job swaps, and what it swaps them for, is the run property table's to say
9
+ * (`formatting/runProperties`); this module holds the surgery around it.
10
+ *
8
11
  * The display values produced here are used on screen only. What goes back into the document is
9
12
  * always the rPr string.
10
13
  */
11
14
  import { type RunFormat } from "../model/format";
15
+ import { type EditableRunKey, type RunEdit } from "./formatting";
16
+ export type { RunEdit } from "./formatting";
17
+ export { matchesRunEdit } from "./formatting";
12
18
  /** The character formatting that is toggled on and off */
13
19
  export type RunToggle = "bold" | "italic" | "underline" | "strike";
14
- /** A job that changes one piece of character formatting. A null value means withdrawing the setting */
15
- export type RunEdit = {
16
- kind: "toggle";
17
- toggle: RunToggle;
18
- on: boolean;
19
- } | {
20
- kind: "fontSize";
21
- pt: number | null;
22
- } | {
23
- kind: "fontFamily";
24
- name: string | null;
25
- } | {
26
- kind: "color";
27
- hex: string | null;
28
- } | {
29
- kind: "background";
30
- hex: string | null;
31
- };
32
20
  /** The formatting a run holds. The original XML and the display values derived from that XML form a pair */
33
21
  export interface RunProps {
34
22
  /** The whole `<w:rPr>...</w:rPr>` XML. null for a run with no formatting */
@@ -40,12 +28,11 @@ export declare function readRunProps(rPr: string | null): RunFormat | null;
40
28
  /**
41
29
  * The rPr and display values after changing one piece of character formatting.
42
30
  *
43
- * `pPr` is used only to see whether the paragraph points at a style.
31
+ * `inherited` is everything the layers below the run lay down (`inheritedRunFormat` in
32
+ * `formatting`), which decides whether an off is pinned down or the element simply removed.
44
33
  * null for an rPr whose shape could not be made out, or for a value that cannot be written into
45
34
  * the document, in which case the original is left untouched.
46
35
  */
47
- export declare function editRunProps(current: RunProps, pPr: string | null, edit: RunEdit): RunProps | null;
36
+ export declare function editRunProps<K extends EditableRunKey>(current: RunProps, inherited: RunFormat, edit: RunEdit<K>): RunProps | null;
48
37
  /** Whether one of the toggled formats is on in these display values */
49
38
  export declare function isRunToggleOn(format: RunFormat | null, toggle: RunToggle): boolean;
50
- /** Whether this text is already in the state the job wants. If it already is, we leave it untouched */
51
- export declare function matchesRunEdit(format: RunFormat | null, edit: RunEdit): boolean;
@@ -1,139 +1,20 @@
1
1
  // src/docx/runProps.ts
2
2
  import { toRunFormat } from "../model/format.js";
3
- import {
4
- attrPairs,
5
- attrValue,
6
- elementXml
7
- } from "../ooxml/element.js";
8
- import { wName } from "../ooxml/names.js";
9
- import { setAttr } from "../ooxml/precedence.js";
10
- import { normalizeHex } from "../ooxml/units.js";
11
- import { isEastAsianFontName } from "../styles/fontStack.js";
12
- import { fontNamesOf, readRunFormat } from "./formatting.js";
13
3
  import {
14
4
  parseProps,
15
5
  parsePropsXml,
16
- propsChild,
17
- RUN_PR_ORDER,
18
- renderProps,
19
- setPropsChild
20
- } from "./propsXml.js";
21
- var TOGGLE_CHILDREN = {
22
- bold: ["b", "bCs"],
23
- italic: ["i", "iCs"],
24
- underline: ["u"],
25
- strike: ["strike"]
26
- };
27
- var TOGGLE_ON_VALUE = {
28
- bold: null,
29
- italic: null,
30
- underline: "single",
31
- strike: null
32
- };
33
- var TOGGLE_OFF_VALUE = {
34
- bold: "0",
35
- italic: "0",
36
- underline: "none",
37
- strike: "0"
38
- };
39
- var MAX_FONT_SIZE_PT = 819;
40
- function valXml(name, value) {
41
- return elementXml(wName(name), value === null ? [] : [[wName("val"), value]]);
42
- }
43
- function shadingXml(fill) {
44
- return elementXml(wName("shd"), [
45
- [wName("val"), "clear"],
46
- [wName("color"), "auto"],
47
- [wName("fill"), fill]
48
- ]);
49
- }
50
- function halfPoints(pt) {
51
- if (!Number.isFinite(pt) || pt <= 0 || pt > MAX_FONT_SIZE_PT) return null;
52
- const half = pt * 2;
53
- return Number.isInteger(half) ? half : null;
54
- }
55
- var LATIN_SLOTS = ["ascii", "hAnsi"];
56
- var EAST_ASIAN_SLOTS = [...LATIN_SLOTS, "eastAsia"];
57
- function fontSlots(name, hasComplexScript) {
58
- const slots = isEastAsianFontName(name) ? EAST_ASIAN_SLOTS : LATIN_SLOTS;
59
- return hasComplexScript ? [...slots, "cs"] : slots;
60
- }
61
- function fontName(value) {
62
- const name = value.trim();
63
- return name.length > 0 && !/["';<>&]/.test(name) ? name : null;
64
- }
65
- function rFontsXml(current, name) {
66
- const el = current === null ? null : parsePropsXml(current);
67
- if (current !== null && !el) return null;
68
- const attrs = attrPairs(el);
69
- const slots = fontSlots(name, attrValue(attrs, "cs") !== null);
70
- const kept = slots.reduce(
71
- (rest, slot) => setAttr(rest, "rFonts", slot, null),
72
- attrs
73
- );
74
- return elementXml(wName("rFonts"), [
75
- ...slots.map((slot) => [wName(slot), name]),
76
- ...kept
77
- ]);
78
- }
79
- function hasChild(xml, name) {
80
- if (xml === null) return false;
81
- const props = parseProps(xml);
82
- return props !== null && propsChild(props.children, name) !== void 0;
83
- }
6
+ renderProps
7
+ } from "../ooxml/props.js";
8
+ import {
9
+ EMPTY_RUN_PROPS,
10
+ RUN_PROPERTIES,
11
+ readRunFormat,
12
+ runChildEdits,
13
+ withChildEdits
14
+ } from "./formatting.js";
15
+ import { matchesRunEdit } from "./formatting.js";
84
16
  function propsOf(rPr) {
85
- if (rPr === null) return { tag: "w:rPr", attrs: null, children: [] };
86
- return parseProps(rPr);
87
- }
88
- function offEdit(name, value, inherited) {
89
- return [name, inherited ? valXml(name, value) : null];
90
- }
91
- function childEdits(edit, inherited, rFonts) {
92
- switch (edit.kind) {
93
- case "toggle": {
94
- const names = TOGGLE_CHILDREN[edit.toggle];
95
- if (edit.on) {
96
- const value = TOGGLE_ON_VALUE[edit.toggle];
97
- return names.map((name) => [name, valXml(name, value)]);
98
- }
99
- const off = TOGGLE_OFF_VALUE[edit.toggle];
100
- return names.map((name) => offEdit(name, off, inherited));
101
- }
102
- case "fontSize": {
103
- if (edit.pt === null) {
104
- return [
105
- ["sz", null],
106
- ["szCs", null]
107
- ];
108
- }
109
- const half = halfPoints(edit.pt);
110
- if (half === null) return null;
111
- return [
112
- ["sz", valXml("sz", `${half}`)],
113
- ["szCs", valXml("szCs", `${half}`)]
114
- ];
115
- }
116
- case "fontFamily": {
117
- if (edit.name === null) return [["rFonts", null]];
118
- const name = fontName(edit.name);
119
- if (name === null) return null;
120
- const xml = rFontsXml(rFonts, name);
121
- return xml === null ? null : [["rFonts", xml]];
122
- }
123
- case "color": {
124
- if (edit.hex === null) return [offEdit("color", "auto", inherited)];
125
- const hex = normalizeHex(edit.hex);
126
- return hex === null ? null : [["color", valXml("color", hex)]];
127
- }
128
- case "background": {
129
- const highlight = offEdit("highlight", "none", inherited);
130
- if (edit.hex === null) {
131
- return [["shd", inherited ? shadingXml("auto") : null], highlight];
132
- }
133
- const hex = normalizeHex(edit.hex);
134
- return hex === null ? null : [["shd", shadingXml(hex)], highlight];
135
- }
136
- }
17
+ return rPr === null ? EMPTY_RUN_PROPS : parseProps(rPr);
137
18
  }
138
19
  function readRunProps(rPr) {
139
20
  return rPr === null ? null : readRunFormat(parsePropsXml(rPr));
@@ -154,50 +35,16 @@ function nextProps(rPr, previous) {
154
35
  }
155
36
  return { rPr, format };
156
37
  }
157
- function editRunProps(current, pPr, edit) {
38
+ function editRunProps(current, inherited, edit) {
158
39
  const props = propsOf(current.rPr);
159
40
  if (!props) return null;
160
- const inherited = propsChild(props.children, "rStyle") !== void 0 || hasChild(pPr, "pStyle");
161
- const rFonts = propsChild(props.children, "rFonts")?.xml ?? null;
162
- const edits = childEdits(edit, inherited, rFonts);
41
+ const edits = runChildEdits(edit, { rPr: props, inherited });
163
42
  if (!edits) return null;
164
- const rPr = renderProps(
165
- edits.reduce(
166
- (kept, [name, xml]) => setPropsChild(kept, name, xml, RUN_PR_ORDER),
167
- props
168
- )
169
- );
43
+ const rPr = renderProps(withChildEdits(props, edits));
170
44
  return nextProps(rPr === "" ? null : rPr, current);
171
45
  }
172
46
  function isRunToggleOn(format, toggle) {
173
- if (!format) return false;
174
- if (toggle === "bold") return format.bold === true;
175
- if (toggle === "italic") return format.italic === true;
176
- if (toggle === "strike") return format.strike === true;
177
- return format.underline !== void 0;
178
- }
179
- function matchesRunEdit(format, edit) {
180
- switch (edit.kind) {
181
- case "toggle":
182
- return isRunToggleOn(format, edit.toggle) === edit.on;
183
- case "fontSize":
184
- return (format?.fontSizePt ?? null) === edit.pt;
185
- case "fontFamily": {
186
- const names = fontNamesOf(format?.fontFamily);
187
- if (edit.name === null) return names.length === 0;
188
- return names.length === 1 && names[0] === fontName(edit.name);
189
- }
190
- case "color": {
191
- const current = format?.color ?? null;
192
- if (current === null || edit.hex === null) return current === edit.hex;
193
- return normalizeHex(current) === normalizeHex(edit.hex);
194
- }
195
- case "background": {
196
- if (format?.highlight !== void 0) return false;
197
- const fill = format?.background ? normalizeHex(format.background) : null;
198
- return fill === (edit.hex === null ? null : normalizeHex(edit.hex));
199
- }
200
- }
47
+ return RUN_PROPERTIES[toggle].isOn(format ?? {});
201
48
  }
202
49
  export {
203
50
  editRunProps,
package/dist/docx/scan.js CHANGED
@@ -1,49 +1,19 @@
1
1
  // src/docx/scan.ts
2
2
  import { DocxImportError } from "../ooxml/errors.js";
3
+ import { readTag } from "../ooxml/tagScan.js";
3
4
  import { localPart } from "../ooxml/xml.js";
4
- function skipPast(source, from, marker) {
5
- const at = source.indexOf(marker, from);
6
- if (at === -1) {
7
- throw new DocxImportError("malformed-xml", `no ${marker} to close a tag`);
8
- }
9
- return at + marker.length;
10
- }
11
- function skipNonTag(source, lt) {
12
- if (source.startsWith("<?", lt)) return skipPast(source, lt, "?>");
13
- if (source.startsWith("<!--", lt)) return skipPast(source, lt, "-->");
14
- if (source.startsWith("<![CDATA[", lt)) return skipPast(source, lt, "]]>");
15
- if (source.startsWith("<!", lt)) return skipPast(source, lt, ">");
16
- return null;
17
- }
18
- function readOpenTag(source, lt) {
19
- let i = lt + 1;
20
- const nameStart = i;
21
- while (i < source.length && !" \r\n/>".includes(source[i])) i += 1;
22
- const name = source.slice(nameStart, i);
23
- if (!name)
24
- throw new DocxImportError("malformed-xml", "a tag has an empty name");
25
- let quote = null;
26
- while (i < source.length) {
27
- const ch = source[i];
28
- if (quote !== null) {
29
- if (ch === quote) quote = null;
30
- } else if (ch === '"' || ch === "'") {
31
- quote = ch;
32
- } else if (ch === ">") {
33
- return { name, end: i + 1, selfClosing: source[i - 1] === "/" };
34
- }
35
- i += 1;
5
+ function tagAt(source, lt) {
6
+ const tag = readTag(source, lt);
7
+ if (!tag) {
8
+ throw new DocxImportError(
9
+ "malformed-xml",
10
+ "a tag cannot be read to its end"
11
+ );
36
12
  }
37
- throw new DocxImportError("malformed-xml", "a tag is left unclosed");
38
- }
39
- function readCloseTag(source, lt) {
40
- const gt = source.indexOf(">", lt);
41
- if (gt === -1)
42
- throw new DocxImportError("malformed-xml", "a tag is left unclosed");
43
- return { name: source.slice(lt + 2, gt).trim(), end: gt + 1 };
13
+ return tag;
44
14
  }
45
15
  function isBodyTag(tag, depth) {
46
- return localPart(tag.name) === "body" && depth === 1 && !tag.selfClosing;
16
+ return localPart(tag.name) === "body" && depth === 1 && tag.kind === "open";
47
17
  }
48
18
  function scanBody(source) {
49
19
  const stack = [];
@@ -60,33 +30,31 @@ function scanBody(source) {
60
30
  while (i < source.length) {
61
31
  const lt = source.indexOf("<", i);
62
32
  if (lt === -1) break;
63
- const skipped = skipNonTag(source, lt);
64
- if (skipped !== null) {
65
- i = skipped;
33
+ const tag = tagAt(source, lt);
34
+ if (tag.kind === "other") {
35
+ i = tag.end;
66
36
  continue;
67
37
  }
68
- if (source.startsWith("</", lt)) {
69
- const tag2 = readCloseTag(source, lt);
38
+ if (tag.kind === "close") {
70
39
  const opened = stack.pop();
71
- if (opened !== tag2.name) {
40
+ if (opened !== tag.name) {
72
41
  throw new DocxImportError(
73
42
  "malformed-xml",
74
- `mismatched tags: ${opened} vs ${tag2.name}`
43
+ `mismatched tags: ${opened} vs ${tag.name}`
75
44
  );
76
45
  }
77
- if (atBodyLevel()) takeBlock(tag2.name, tag2.end);
78
- i = tag2.end;
46
+ if (atBodyLevel()) takeBlock(tag.name, tag.end);
47
+ i = tag.end;
79
48
  continue;
80
49
  }
81
- const tag = readOpenTag(source, lt);
82
50
  if (bodyDepth === null && isBodyTag(tag, stack.length)) {
83
51
  stack.push(tag.name);
84
52
  bodyDepth = stack.length;
85
53
  contentStart = tag.end;
86
54
  sliceStart = tag.end;
87
55
  } else {
88
- if (atBodyLevel() && tag.selfClosing) takeBlock(tag.name, tag.end);
89
- if (!tag.selfClosing) stack.push(tag.name);
56
+ if (atBodyLevel() && tag.kind === "empty") takeBlock(tag.name, tag.end);
57
+ if (tag.kind === "open") stack.push(tag.name);
90
58
  }
91
59
  i = tag.end;
92
60
  }
package/dist/docx/sdt.js CHANGED
@@ -1,14 +1,14 @@
1
1
  // src/docx/sdt.ts
2
- import { elementXml, emptyTagXml, openTagXml } from "../ooxml/element.js";
2
+ import { elementXml, openTagXml } from "../ooxml/element.js";
3
3
  import { wName } from "../ooxml/names.js";
4
- import { wAttr } from "../ooxml/units.js";
5
- import { attrString, elementChildren, serializeXml } from "../ooxml/xml.js";
6
4
  import {
7
5
  parseProps,
8
6
  propsChild,
9
- renderProps,
10
- setPropsChild
11
- } from "./propsXml.js";
7
+ renderElement,
8
+ setChild
9
+ } from "../ooxml/props.js";
10
+ import { wAttr } from "../ooxml/units.js";
11
+ import { attrString, elementChildren, serializeXml } from "../ooxml/xml.js";
12
12
  var CONTENTS_LOCKED = ["contentLocked", "sdtContentLocked"];
13
13
  var DELETION_LOCKED = ["sdtLocked", "sdtContentLocked"];
14
14
  function lockValue(sdtPr) {
@@ -39,18 +39,6 @@ function readSdtWrapper(el) {
39
39
  deletionLocked: val !== null && DELETION_LOCKED.includes(val)
40
40
  };
41
41
  }
42
- var SDT_ORDER = ["sdtPr", "sdtEndPr", "sdtContent"];
43
- var SDT_PR_ORDER = [
44
- "rPr",
45
- "alias",
46
- "tag",
47
- "id",
48
- "lock",
49
- "placeholder",
50
- "showingPlcHdr",
51
- "dataBinding",
52
- "temporary"
53
- ];
54
42
  function newControlId() {
55
43
  return Math.floor(Math.random() * 2147483647);
56
44
  }
@@ -67,28 +55,15 @@ function namesNothing(prefix) {
67
55
  if (!props || sdt.children.length !== 1) return false;
68
56
  return props.children.every((child) => NAMES_NOTHING.includes(child.name));
69
57
  }
70
- function emptyProps(props) {
71
- return emptyTagXml(props.tag, props.attrs);
72
- }
73
58
  function editSdtPrefix(prefix, edits) {
74
59
  const sdt = parseProps(`${prefix}</w:sdt>`);
75
60
  const sdtPr = sdt && propsChild(sdt.children, "sdtPr");
76
61
  const props = sdtPr ? parseProps(sdtPr.xml) : null;
77
62
  if (!sdt || !props) return null;
78
- const rendered = renderProps(
79
- edits.reduce(
80
- (kept, [name, xml]) => setPropsChild(kept, name, xml, SDT_PR_ORDER),
81
- props
82
- )
83
- );
84
- return renderPrefix(
85
- setPropsChild(
86
- sdt,
87
- "sdtPr",
88
- rendered === "" ? emptyProps(props) : rendered,
89
- SDT_ORDER
90
- )
63
+ const rendered = renderElement(
64
+ edits.reduce((kept, [name, xml]) => setChild(kept, name, xml), props)
91
65
  );
66
+ return renderPrefix(setChild(sdt, "sdtPr", rendered));
92
67
  }
93
68
  function copiedControlPrefix(prefix, id) {
94
69
  const copied = editSdtPrefix(prefix, [
@@ -1,5 +1,6 @@
1
1
  /**
2
- * Turning the lock of a content control on and off inside the opening XML it goes back out as.
2
+ * Turning the lock of a content control on and off inside the opening XML it goes back out as,
3
+ * and writing the opening of a control that is locked from the start.
3
4
  *
4
5
  * Nothing but the `w:lock` child of the `w:sdtPr` moves. The id Word gave the control, the alias
5
6
  * and tag it goes by, and the `w:dataBinding` that ties it to the file's own XML all stay, which
@@ -10,6 +11,12 @@
10
11
  * A control Word gave nothing but a guard against deletion (`sdtLocked`) is therefore shut by
11
12
  * being given the stricter value, and lifting that lock afterwards opens both clauses.
12
13
  */
14
+ /**
15
+ * The opening of a new control that shuts both clauses, which is what a stretch being locked is
16
+ * wrapped in. It carries its id and the lock and nothing else, so a later lock may take it over
17
+ * (`namesNothing` in `./sdt`).
18
+ */
19
+ export declare function lockedControlPrefix(id: number): string;
13
20
  /**
14
21
  * The opening of the control with its lock shut or lifted.
15
22
  * null for a prefix whose shape cannot be made out, which leaves the caller to decide what to do