@portone/docx-editor 0.1.1 → 0.2.1

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 (138) hide show
  1. package/CHANGELOG.md +82 -0
  2. package/CONTRIBUTING.md +4 -0
  3. package/README.md +17 -5
  4. package/dist/DocxEditor.d.ts +34 -10
  5. package/dist/DocxEditor.js +69 -24
  6. package/dist/core.d.ts +2 -0
  7. package/dist/core.js +2 -0
  8. package/dist/docx/commentOnlyChange.d.ts +47 -0
  9. package/dist/docx/commentOnlyChange.js +162 -0
  10. package/dist/docx/comments/constants.d.ts +11 -3
  11. package/dist/docx/comments/constants.js +10 -3
  12. package/dist/docx/comments/contentTypes.d.ts +7 -0
  13. package/dist/docx/comments/contentTypes.js +38 -0
  14. package/dist/docx/comments/grammar.d.ts +80 -0
  15. package/dist/docx/comments/grammar.js +213 -0
  16. package/dist/docx/comments/model.d.ts +2 -0
  17. package/dist/docx/comments/model.js +3 -0
  18. package/dist/docx/comments/people.d.ts +39 -0
  19. package/dist/docx/comments/people.js +196 -0
  20. package/dist/docx/comments/reading.d.ts +18 -1
  21. package/dist/docx/comments/reading.js +33 -20
  22. package/dist/docx/comments/verifying.d.ts +44 -0
  23. package/dist/docx/comments/verifying.js +206 -0
  24. package/dist/docx/comments/writing.d.ts +4 -1
  25. package/dist/docx/comments/writing.js +84 -77
  26. package/dist/docx/exportDocx.js +2 -1
  27. package/dist/docx/formatting/direct.js +3 -6
  28. package/dist/docx/headersFooters.js +8 -10
  29. package/dist/docx/importParagraph.js +1 -0
  30. package/dist/docx/notes.js +3 -5
  31. package/dist/docx/paraProps.js +51 -46
  32. package/dist/docx/propsXml.d.ts +20 -2
  33. package/dist/docx/propsXml.js +72 -32
  34. package/dist/docx/relationships.js +8 -3
  35. package/dist/docx/runProps.js +34 -34
  36. package/dist/docx/sdt.js +16 -14
  37. package/dist/docx/serializeBlock.js +1 -2
  38. package/dist/docx/serializeParagraph.d.ts +2 -1
  39. package/dist/docx/serializeParagraph.js +25 -14
  40. package/dist/docx/serializeTable.js +38 -24
  41. package/dist/docx/storyProjection.d.ts +36 -0
  42. package/dist/docx/storyProjection.js +23 -0
  43. package/dist/docx/tableFormatting/editing.js +49 -73
  44. package/dist/docx/tableTemplate.js +24 -3
  45. package/dist/docx/theme.d.ts +0 -6
  46. package/dist/docx/theme.js +0 -8
  47. package/dist/editor/clipboard/images.js +17 -3
  48. package/dist/editor/clipboard/inlineFormatting.d.ts +7 -0
  49. package/dist/editor/clipboard/inlineFormatting.js +1 -0
  50. package/dist/editor/commands/breakCommands.js +6 -2
  51. package/dist/editor/commands/canRunCommand.d.ts +3 -2
  52. package/dist/editor/commands/canRunCommand.js +2 -2
  53. package/dist/editor/commands/comments/editing.js +9 -10
  54. package/dist/editor/commands/comments/model.d.ts +9 -0
  55. package/dist/editor/commands/comments/reading.d.ts +7 -0
  56. package/dist/editor/commands/comments/reading.js +14 -0
  57. package/dist/editor/commands/formatting/editing.js +6 -1
  58. package/dist/editor/commands/historyCommands.js +14 -5
  59. package/dist/editor/commands/index.d.ts +7 -1
  60. package/dist/editor/commands/index.js +4 -0
  61. package/dist/editor/commands/linkCommands.js +2 -0
  62. package/dist/editor/commands/lockCommands.js +12 -1
  63. package/dist/editor/commands/tabCommands.js +4 -2
  64. package/dist/editor/createEditor.d.ts +13 -3
  65. package/dist/editor/createEditor.js +23 -15
  66. package/dist/editor/externalClipboard.js +166 -4
  67. package/dist/editor/insertImage.js +6 -2
  68. package/dist/editor/insertTable.js +2 -1
  69. package/dist/editor/paragraphEdits.d.ts +2 -0
  70. package/dist/editor/paragraphEdits.js +2 -0
  71. package/dist/editor/plugins/documentProtection.d.ts +21 -0
  72. package/dist/editor/plugins/documentProtection.js +44 -0
  73. package/dist/editor/plugins/imagePaste.js +4 -1
  74. package/dist/editor/plugins/keymap.d.ts +11 -1
  75. package/dist/editor/plugins/keymap.js +24 -4
  76. package/dist/editor/plugins/lockedContent.d.ts +4 -2
  77. package/dist/editor/plugins/lockedContent.js +2 -2
  78. package/dist/editor/plugins/tableContextMenu.js +2 -1
  79. package/dist/editor/plugins/textContextMenu.js +6 -1
  80. package/dist/index.d.ts +2 -0
  81. package/dist/numbering/listTemplate.js +32 -10
  82. package/dist/ooxml/element.d.ts +52 -0
  83. package/dist/ooxml/element.js +49 -0
  84. package/dist/ooxml/fragment.d.ts +53 -0
  85. package/dist/ooxml/fragment.js +76 -0
  86. package/dist/ooxml/image.js +4 -3
  87. package/dist/ooxml/names.d.ts +27 -0
  88. package/dist/ooxml/names.js +29 -0
  89. package/dist/ooxml/precedence.d.ts +31 -0
  90. package/dist/ooxml/precedence.js +51 -0
  91. package/dist/ooxml/xml.d.ts +31 -0
  92. package/dist/ooxml/xml.js +27 -0
  93. package/dist/page/blockKinds.d.ts +42 -0
  94. package/dist/page/blockKinds.js +0 -0
  95. package/dist/page/measureBlocks.d.ts +14 -1
  96. package/dist/page/measureBlocks.js +31 -11
  97. package/dist/page/pageDecorations.d.ts +16 -0
  98. package/dist/page/pageDecorations.js +1 -0
  99. package/dist/page/pageLayout.d.ts +8 -43
  100. package/dist/page/pageLayout.js +28 -51
  101. package/dist/page/tableMeasurements.d.ts +10 -2
  102. package/dist/page/tableMeasurements.js +20 -16
  103. package/dist/page/usePageLayout.d.ts +15 -0
  104. package/dist/page/usePageLayout.js +30 -2
  105. package/dist/schema/attrRoles.d.ts +35 -0
  106. package/dist/schema/attrRoles.js +106 -0
  107. package/dist/schema/docxSchema.d.ts +6 -0
  108. package/dist/schema/docxSchema.js +216 -100
  109. package/dist/schema/editGuard.d.ts +99 -0
  110. package/dist/schema/editGuard.js +43 -0
  111. package/dist/schema/guards.d.ts +68 -0
  112. package/dist/schema/guards.js +82 -0
  113. package/dist/schema/locks.d.ts +9 -8
  114. package/dist/schema/locks.js +32 -16
  115. package/dist/schema/preservedGuards.d.ts +30 -0
  116. package/dist/schema/preservedGuards.js +50 -0
  117. package/dist/schema/protection.d.ts +84 -0
  118. package/dist/schema/protection.js +171 -0
  119. package/dist/schema/protectionState.d.ts +20 -0
  120. package/dist/schema/protectionState.js +37 -0
  121. package/dist/schema/sourceEquality.d.ts +27 -0
  122. package/dist/schema/sourceEquality.js +65 -0
  123. package/dist/table/cellFormatting.js +5 -2
  124. package/dist/table/commands.d.ts +2 -2
  125. package/dist/table/commands.js +2 -2
  126. package/dist/table/index.d.ts +2 -2
  127. package/dist/table/merge.d.ts +2 -2
  128. package/dist/table/merge.js +3 -3
  129. package/dist/ui/CommentsPanel.d.ts +3 -3
  130. package/dist/ui/CommentsPanel.js +26 -18
  131. package/dist/ui/LinkCard.d.ts +1 -2
  132. package/dist/ui/LinkCard.js +3 -4
  133. package/dist/ui/TextMenu.js +15 -10
  134. package/package.json +5 -2
  135. package/dist/editor/plugins/bookmarkProtection.d.ts +0 -3
  136. package/dist/editor/plugins/bookmarkProtection.js +0 -28
  137. package/dist/editor/plugins/noteProtection.d.ts +0 -3
  138. package/dist/editor/plugins/noteProtection.js +0 -25
@@ -1,5 +1,10 @@
1
1
  // src/docx/propsXml.ts
2
- import { elementChildren, localPart, parseXml, W_NS } from "../ooxml/xml.js";
2
+ import {
3
+ elementChildren,
4
+ localPart,
5
+ namespaceDecls,
6
+ parseXml
7
+ } from "../ooxml/xml.js";
3
8
  var RUN_PR_ORDER = [
4
9
  "rStyle",
5
10
  "rFonts",
@@ -179,6 +184,12 @@ function attrsOf(source, tag) {
179
184
  const attrs = source.slice(tag.nameEnd, tag.end - closeLength).trim();
180
185
  return attrs.length > 0 ? attrs : null;
181
186
  }
187
+ function innerXml(xml) {
188
+ const open = readTag(xml, 0);
189
+ if (!open || xml[0] !== "<" || open.kind !== "open") return "";
190
+ const close = xml.lastIndexOf("</");
191
+ return close > open.end ? xml.slice(open.end, close) : "";
192
+ }
182
193
  function parseProps(xml) {
183
194
  const open = readTag(xml, 0);
184
195
  if (!open || xml[0] !== "<") return null;
@@ -192,6 +203,11 @@ function parseProps(xml) {
192
203
  let childStart = -1;
193
204
  let childName = "";
194
205
  let i = open.end;
206
+ let gapStart = open.end;
207
+ const gapBefore = (start) => {
208
+ const gap = xml.slice(gapStart, start);
209
+ return gap.length > 0 ? { before: gap } : {};
210
+ };
195
211
  while (i < xml.length) {
196
212
  const lt = xml.indexOf("<", i);
197
213
  if (lt === -1) return null;
@@ -204,15 +220,18 @@ function parseProps(xml) {
204
220
  if (depth === 0) {
205
221
  if (tag.kind === "close") {
206
222
  if (tag.name !== open.name || tag.end !== xml.length) return null;
207
- return { tag: open.name, attrs, children };
223
+ const tail = xml.slice(gapStart, lt);
224
+ return tail.length > 0 ? { tag: open.name, attrs, children, tail } : { tag: open.name, attrs, children };
208
225
  }
209
226
  childStart = lt;
210
227
  childName = tag.name;
211
228
  if (tag.kind === "empty") {
212
229
  children.push({
213
230
  name: localPart(childName),
214
- xml: xml.slice(childStart, tag.end)
231
+ xml: xml.slice(childStart, tag.end),
232
+ ...gapBefore(childStart)
215
233
  });
234
+ gapStart = tag.end;
216
235
  } else {
217
236
  depth = 1;
218
237
  }
@@ -223,8 +242,10 @@ function parseProps(xml) {
223
242
  if (depth === 0) {
224
243
  children.push({
225
244
  name: localPart(childName),
226
- xml: xml.slice(childStart, tag.end)
245
+ xml: xml.slice(childStart, tag.end),
246
+ ...gapBefore(childStart)
227
247
  });
248
+ gapStart = tag.end;
228
249
  }
229
250
  }
230
251
  i = tag.end;
@@ -232,9 +253,11 @@ function parseProps(xml) {
232
253
  return null;
233
254
  }
234
255
  function renderProps(props) {
235
- if (props.children.length === 0) return "";
256
+ const tail = props.tail ?? "";
257
+ if (props.children.length === 0 && tail.trim().length === 0) return "";
236
258
  const open = props.attrs ? `<${props.tag} ${props.attrs}>` : `<${props.tag}>`;
237
- return open + props.children.map((child) => child.xml).join("") + `</${props.tag}>`;
259
+ const inner = props.children.map((child) => (child.before ?? "") + child.xml).join("");
260
+ return open + inner + tail + `</${props.tag}>`;
238
261
  }
239
262
  function insertIndex(children, name, order) {
240
263
  const target = order.indexOf(name);
@@ -248,37 +271,53 @@ function insertIndex(children, name, order) {
248
271
  }
249
272
  return children.length;
250
273
  }
251
- function setPropsChild(children, name, xml, order) {
252
- const without = children.filter((child2) => child2.name !== name);
253
- if (xml === null) return without;
254
- const child = { name, xml };
255
- const at = children.findIndex((entry) => entry.name === name);
256
- if (at === -1) {
257
- const index = insertIndex(without, name, order);
258
- return [...without.slice(0, index), child, ...without.slice(index)];
274
+ function dropChildren(children, name) {
275
+ const kept = [];
276
+ let carried = "";
277
+ for (const child of children) {
278
+ const before = carried + (child.before ?? "");
279
+ if (child.name === name) {
280
+ carried = before;
281
+ continue;
282
+ }
283
+ carried = "";
284
+ kept.push(
285
+ before === "" ? { name: child.name, xml: child.xml } : { ...child, before }
286
+ );
259
287
  }
260
- return [
261
- ...children.slice(0, at),
262
- child,
263
- ...children.slice(at + 1).filter((entry) => entry.name !== name)
264
- ];
288
+ return { kept, carried };
265
289
  }
266
- function propsChild(children, name) {
267
- return children.find((child) => child.name === name);
290
+ function tailWith(props, carried) {
291
+ const tail = carried + (props.tail ?? "");
292
+ return tail === "" ? {} : { tail };
268
293
  }
269
- function namespaceDecls(xml) {
270
- const prefixes = /* @__PURE__ */ new Set(["w"]);
271
- for (const [, prefix] of xml.matchAll(/<\/?([A-Za-z_][\w.-]*):/g)) {
272
- prefixes.add(prefix);
294
+ function setPropsChild(props, name, xml, order) {
295
+ const at = props.children.findIndex((entry) => entry.name === name);
296
+ if (at !== -1 && xml !== null) {
297
+ const rest = dropChildren(props.children.slice(at + 1), name);
298
+ return {
299
+ ...props,
300
+ children: [
301
+ ...props.children.slice(0, at),
302
+ { ...props.children[at], xml },
303
+ ...rest.kept
304
+ ],
305
+ ...tailWith(props, rest.carried)
306
+ };
273
307
  }
274
- for (const [, prefix] of xml.matchAll(/[\s"']([A-Za-z_][\w.-]*):[\w.-]+=/g)) {
275
- prefixes.add(prefix);
308
+ const { kept, carried } = dropChildren(props.children, name);
309
+ if (xml === null) {
310
+ return { ...props, children: kept, ...tailWith(props, carried) };
276
311
  }
277
- prefixes.delete("xml");
278
- prefixes.delete("xmlns");
279
- return Array.from(prefixes).map(
280
- (prefix) => `xmlns:${prefix}="${prefix === "w" ? W_NS : `urn:docx-editor:${prefix}`}"`
281
- ).join(" ");
312
+ const index = insertIndex(kept, name, order);
313
+ return {
314
+ ...props,
315
+ children: [...kept.slice(0, index), { name, xml }, ...kept.slice(index)],
316
+ ...tailWith(props, carried)
317
+ };
318
+ }
319
+ function propsChild(children, name) {
320
+ return children.find((child) => child.name === name);
282
321
  }
283
322
  function parsePropsXml(xml) {
284
323
  try {
@@ -294,6 +333,7 @@ export {
294
333
  TBL_PR_ORDER,
295
334
  TC_PR_ORDER,
296
335
  TR_PR_ORDER,
336
+ innerXml,
297
337
  parseProps,
298
338
  parsePropsXml,
299
339
  propsChild,
@@ -1,10 +1,10 @@
1
1
  // src/docx/relationships.ts
2
+ import { elementXml } from "../ooxml/element.js";
2
3
  import { DocxExportError } from "../ooxml/errors.js";
3
4
  import {
4
5
  decodeUtf8,
5
6
  elementChildren,
6
7
  encodeUtf8,
7
- escapeXml,
8
8
  parseXml
9
9
  } from "../ooxml/xml.js";
10
10
  var RELATIONSHIPS_NS = "http://schemas.openxmlformats.org/package/2006/relationships";
@@ -48,8 +48,13 @@ function takeRelId(taken) {
48
48
  }
49
49
  }
50
50
  function relationshipXml(added) {
51
- const mode = added.external ? ' TargetMode="External"' : "";
52
- return `<Relationship Id="${added.id}" Type="${added.type}" Target="${escapeXml(added.target)}"${mode}/>`;
51
+ const mode = added.external ? [["TargetMode", "External"]] : [];
52
+ return elementXml("Relationship", [
53
+ ["Id", added.id],
54
+ ["Type", added.type],
55
+ ["Target", added.target],
56
+ ...mode
57
+ ]);
53
58
  }
54
59
  function relationshipsPart(original, added) {
55
60
  const entries = added.map(relationshipXml).join("");
@@ -1,7 +1,13 @@
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";
3
10
  import { normalizeHex } from "../ooxml/units.js";
4
- import { escapeXml, localPart } from "../ooxml/xml.js";
5
11
  import { isEastAsianFontName } from "../styles/fontStack.js";
6
12
  import { fontNamesOf, readRunFormat } from "./formatting.js";
7
13
  import {
@@ -12,7 +18,6 @@ import {
12
18
  renderProps,
13
19
  setPropsChild
14
20
  } from "./propsXml.js";
15
- import { THEME_ATTRS } from "./theme.js";
16
21
  var TOGGLE_CHILDREN = {
17
22
  bold: ["b", "bCs"],
18
23
  italic: ["i", "iCs"],
@@ -32,11 +37,15 @@ var TOGGLE_OFF_VALUE = {
32
37
  strike: "0"
33
38
  };
34
39
  var MAX_FONT_SIZE_PT = 819;
35
- function elementXml(name, value) {
36
- return value === null ? `<w:${name}/>` : `<w:${name} w:val="${value}"/>`;
40
+ function valXml(name, value) {
41
+ return elementXml(wName(name), value === null ? [] : [[wName("val"), value]]);
37
42
  }
38
43
  function shadingXml(fill) {
39
- return `<w:shd w:val="clear" w:color="auto" w:fill="${fill}"/>`;
44
+ return elementXml(wName("shd"), [
45
+ [wName("val"), "clear"],
46
+ [wName("color"), "auto"],
47
+ [wName("fill"), fill]
48
+ ]);
40
49
  }
41
50
  function halfPoints(pt) {
42
51
  if (!Number.isFinite(pt) || pt <= 0 || pt > MAX_FONT_SIZE_PT) return null;
@@ -53,29 +62,19 @@ function fontName(value) {
53
62
  const name = value.trim();
54
63
  return name.length > 0 && !/["';<>&]/.test(name) ? name : null;
55
64
  }
56
- function rFontsAttrs(xml) {
57
- if (xml === null) return [];
58
- const el = parsePropsXml(xml);
59
- if (!el) return null;
60
- return Array.from(el.attributes, (attr) => [
61
- attr.name,
62
- attr.value
63
- ]);
64
- }
65
65
  function rFontsXml(current, name) {
66
- const attrs = rFontsAttrs(current);
67
- if (!attrs) return null;
68
- const slots = fontSlots(
69
- name,
70
- attrs.some(([attr]) => localPart(attr) === "cs")
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
71
73
  );
72
- const dropped = slots.flatMap((slot) => [slot, ...THEME_ATTRS[slot] ?? []]);
73
- const kept = attrs.filter(([attr]) => !dropped.includes(localPart(attr)));
74
- const text = [
75
- ...slots.map((slot) => [`w:${slot}`, name]),
74
+ return elementXml(wName("rFonts"), [
75
+ ...slots.map((slot) => [wName(slot), name]),
76
76
  ...kept
77
- ].map(([attr, value]) => `${attr}="${escapeXml(value)}"`).join(" ");
78
- return `<w:rFonts ${text}/>`;
77
+ ]);
79
78
  }
80
79
  function hasChild(xml, name) {
81
80
  if (xml === null) return false;
@@ -87,7 +86,7 @@ function propsOf(rPr) {
87
86
  return parseProps(rPr);
88
87
  }
89
88
  function offEdit(name, value, inherited) {
90
- return [name, inherited ? elementXml(name, value) : null];
89
+ return [name, inherited ? valXml(name, value) : null];
91
90
  }
92
91
  function childEdits(edit, inherited, rFonts) {
93
92
  switch (edit.kind) {
@@ -95,7 +94,7 @@ function childEdits(edit, inherited, rFonts) {
95
94
  const names = TOGGLE_CHILDREN[edit.toggle];
96
95
  if (edit.on) {
97
96
  const value = TOGGLE_ON_VALUE[edit.toggle];
98
- return names.map((name) => [name, elementXml(name, value)]);
97
+ return names.map((name) => [name, valXml(name, value)]);
99
98
  }
100
99
  const off = TOGGLE_OFF_VALUE[edit.toggle];
101
100
  return names.map((name) => offEdit(name, off, inherited));
@@ -110,8 +109,8 @@ function childEdits(edit, inherited, rFonts) {
110
109
  const half = halfPoints(edit.pt);
111
110
  if (half === null) return null;
112
111
  return [
113
- ["sz", elementXml("sz", `${half}`)],
114
- ["szCs", elementXml("szCs", `${half}`)]
112
+ ["sz", valXml("sz", `${half}`)],
113
+ ["szCs", valXml("szCs", `${half}`)]
115
114
  ];
116
115
  }
117
116
  case "fontFamily": {
@@ -124,7 +123,7 @@ function childEdits(edit, inherited, rFonts) {
124
123
  case "color": {
125
124
  if (edit.hex === null) return [offEdit("color", "auto", inherited)];
126
125
  const hex = normalizeHex(edit.hex);
127
- return hex === null ? null : [["color", elementXml("color", hex)]];
126
+ return hex === null ? null : [["color", valXml("color", hex)]];
128
127
  }
129
128
  case "background": {
130
129
  const highlight = offEdit("highlight", "none", inherited);
@@ -162,11 +161,12 @@ function editRunProps(current, pPr, edit) {
162
161
  const rFonts = propsChild(props.children, "rFonts")?.xml ?? null;
163
162
  const edits = childEdits(edit, inherited, rFonts);
164
163
  if (!edits) return null;
165
- const children = edits.reduce(
166
- (kept, [name, xml]) => setPropsChild(kept, name, xml, RUN_PR_ORDER),
167
- props.children
164
+ const rPr = renderProps(
165
+ edits.reduce(
166
+ (kept, [name, xml]) => setPropsChild(kept, name, xml, RUN_PR_ORDER),
167
+ props
168
+ )
168
169
  );
169
- const rPr = renderProps({ ...props, children });
170
170
  return nextProps(rPr === "" ? null : rPr, current);
171
171
  }
172
172
  function isRunToggleOn(format, toggle) {
package/dist/docx/sdt.js CHANGED
@@ -1,4 +1,6 @@
1
1
  // src/docx/sdt.ts
2
+ import { elementXml, emptyTagXml, openTagXml } from "../ooxml/element.js";
3
+ import { wName } from "../ooxml/names.js";
2
4
  import { wAttr } from "../ooxml/units.js";
3
5
  import { attrString, elementChildren, serializeXml } from "../ooxml/xml.js";
4
6
  import {
@@ -31,7 +33,7 @@ function readSdtWrapper(el) {
31
33
  const attrs = attrString(el);
32
34
  const val = lockValue(sdtPr);
33
35
  return {
34
- prefix: (attrs ? `<w:sdt ${attrs}>` : "<w:sdt>") + head.map(serializeXml).join(""),
36
+ prefix: openTagXml(wName("sdt"), attrs) + head.map(serializeXml).join(""),
35
37
  content,
36
38
  contentsLocked: val !== null && CONTENTS_LOCKED.includes(val),
37
39
  deletionLocked: val !== null && DELETION_LOCKED.includes(val)
@@ -53,8 +55,8 @@ function newControlId() {
53
55
  return Math.floor(Math.random() * 2147483647);
54
56
  }
55
57
  function renderPrefix(props) {
56
- const open = props.attrs ? `<${props.tag} ${props.attrs}>` : `<${props.tag}>`;
57
- return open + props.children.map((child) => child.xml).join("");
58
+ const inner = props.children.map((child) => (child.before ?? "") + child.xml).join("");
59
+ return openTagXml(props.tag, props.attrs) + inner + (props.tail ?? "");
58
60
  }
59
61
  var NAMES_NOTHING = ["id", "lock"];
60
62
  function namesNothing(prefix) {
@@ -66,31 +68,31 @@ function namesNothing(prefix) {
66
68
  return props.children.every((child) => NAMES_NOTHING.includes(child.name));
67
69
  }
68
70
  function emptyProps(props) {
69
- return props.attrs ? `<${props.tag} ${props.attrs}/>` : `<${props.tag}/>`;
71
+ return emptyTagXml(props.tag, props.attrs);
70
72
  }
71
73
  function editSdtPrefix(prefix, edits) {
72
74
  const sdt = parseProps(`${prefix}</w:sdt>`);
73
75
  const sdtPr = sdt && propsChild(sdt.children, "sdtPr");
74
76
  const props = sdtPr ? parseProps(sdtPr.xml) : null;
75
77
  if (!sdt || !props) return null;
76
- const children = edits.reduce(
77
- (kept, [name, xml]) => setPropsChild(kept, name, xml, SDT_PR_ORDER),
78
- props.children
78
+ const rendered = renderProps(
79
+ edits.reduce(
80
+ (kept, [name, xml]) => setPropsChild(kept, name, xml, SDT_PR_ORDER),
81
+ props
82
+ )
79
83
  );
80
- const rendered = renderProps({ ...props, children });
81
- return renderPrefix({
82
- ...sdt,
83
- children: setPropsChild(
84
- sdt.children,
84
+ return renderPrefix(
85
+ setPropsChild(
86
+ sdt,
85
87
  "sdtPr",
86
88
  rendered === "" ? emptyProps(props) : rendered,
87
89
  SDT_ORDER
88
90
  )
89
- });
91
+ );
90
92
  }
91
93
  function copiedControlPrefix(prefix, id) {
92
94
  const copied = editSdtPrefix(prefix, [
93
- ["id", `<w:id w:val="${id}"/>`],
95
+ ["id", elementXml(wName("id"), [[wName("val"), `${id}`]])],
94
96
  ["dataBinding", null]
95
97
  ]);
96
98
  return copied ?? prefix;
@@ -18,8 +18,7 @@ function serializeBlock(node, session, refs = NO_EXPORT_REFS) {
18
18
  if (node.type.name === "paragraph") return serializeParagraph(node, refs);
19
19
  if (node.type.name === "table") return serializeTable(node, refs);
20
20
  if (node.type.name === "rawBlock") return preservedXml(node);
21
- if (node.type.name === "bookmarkBlock") return serializeRaw(node, session);
22
- if (node.type.name === "docxRaw") return serializeRaw(node, session);
21
+ if (node.type.isInGroup("preserved")) return serializeRaw(node, session);
23
22
  throw new DocxExportError(
24
23
  "unsupported-content",
25
24
  `block we cannot serialize: ${node.type.name}`
@@ -10,6 +10,7 @@
10
10
  */
11
11
  import type { Node as PMNode } from "prosemirror-model";
12
12
  import { type ExportRefs } from "./exportRefs";
13
- export declare function openTag(name: string, attrs: unknown): string;
13
+ /** The attribute text a node attr holds, which is either what the original wrote or nothing */
14
+ export declare function rawAttrsOf(value: unknown): string | null;
14
15
  export declare function preservedXml(node: PMNode): string;
15
16
  export declare function serializeParagraph(node: PMNode, refs?: ExportRefs): string;
@@ -1,4 +1,5 @@
1
1
  // src/docx/serializeParagraph.ts
2
+ import { elementXml, emptyTagXml, openTagXml } from "../ooxml/element.js";
2
3
  import { DocxExportError } from "../ooxml/errors.js";
3
4
  import {
4
5
  imageDrawingXml,
@@ -6,6 +7,7 @@ import {
6
7
  toImageSrc,
7
8
  withExtent
8
9
  } from "../ooxml/image.js";
10
+ import { wName } from "../ooxml/names.js";
9
11
  import { escapeXml } from "../ooxml/xml.js";
10
12
  import { NO_EXPORT_REFS } from "./exportRefs.js";
11
13
  import { relIdIn, withRelId } from "./hyperlink.js";
@@ -16,11 +18,8 @@ function sameMark(a, b) {
16
18
  function markOf(node, name) {
17
19
  return node.marks.find((mark) => mark.type.name === name) ?? null;
18
20
  }
19
- function openTag(name, attrs) {
20
- return typeof attrs === "string" ? `<${name} ${attrs}>` : `<${name}>`;
21
- }
22
- function emptyTag(name, attrs) {
23
- return typeof attrs === "string" ? `<${name} ${attrs}/>` : `<${name}/>`;
21
+ function rawAttrsOf(value) {
22
+ return typeof value === "string" ? value : null;
24
23
  }
25
24
  function renderImage(node, images) {
26
25
  const extent = toImageExtent(node.attrs.extent);
@@ -51,19 +50,28 @@ function renderInline(node, images) {
51
50
  if (node.isText) {
52
51
  const tab = markOf(node, "tab");
53
52
  return (node.text ?? "").split(" ").flatMap((text, index, pieces) => {
54
- const rendered = text ? [`<w:t xml:space="preserve">${escapeXml(text)}</w:t>`] : [];
55
- return index < pieces.length - 1 ? [...rendered, emptyTag("w:tab", tab?.attrs.tabAttrs)] : rendered;
53
+ const rendered = text ? [
54
+ elementXml(
55
+ wName("t"),
56
+ [["xml:space", "preserve"]],
57
+ [escapeXml(text)]
58
+ )
59
+ ] : [];
60
+ return index < pieces.length - 1 ? [
61
+ ...rendered,
62
+ emptyTagXml(wName("tab"), rawAttrsOf(tab?.attrs.tabAttrs))
63
+ ] : rendered;
56
64
  }).join("");
57
65
  }
58
66
  if (node.type.name === "hardBreak")
59
- return emptyTag("w:br", node.attrs.brAttrs);
67
+ return emptyTagXml(wName("br"), rawAttrsOf(node.attrs.brAttrs));
60
68
  if (node.type.name === "image") return renderImage(node, images);
61
69
  if (node.type.name === "commentReference") {
62
70
  const original = node.attrs.referenceXml;
63
71
  if (typeof original === "string") return original;
64
72
  const id = node.attrs.id;
65
73
  if (typeof id === "string") {
66
- return `<w:commentReference w:id="${escapeXml(id)}"/>`;
74
+ return elementXml(wName("commentReference"), [[wName("id"), id]]);
67
75
  }
68
76
  throw new DocxExportError(
69
77
  "lost-original",
@@ -76,7 +84,7 @@ function renderInline(node, images) {
76
84
  const id = node.attrs.id;
77
85
  const name = node.attrs.kind === "endnote" ? "endnoteReference" : "footnoteReference";
78
86
  if (typeof id === "string") {
79
- return `<w:${name} w:id="${escapeXml(id)}"/>`;
87
+ return elementXml(wName(name), [[wName("id"), id]]);
80
88
  }
81
89
  throw new DocxExportError(
82
90
  "lost-original",
@@ -117,7 +125,10 @@ function addInline(parts, child, images) {
117
125
  );
118
126
  }
119
127
  const name = child.type.name === "commentStart" ? "commentRangeStart" : "commentRangeEnd";
120
- parts.push({ kind: "raw", xml: `<w:${name} w:id="${escapeXml(id)}"/>` });
128
+ parts.push({
129
+ kind: "raw",
130
+ xml: elementXml(wName(name), [[wName("id"), id]])
131
+ });
121
132
  return;
122
133
  }
123
134
  const piece = renderInline(child, images);
@@ -150,7 +161,7 @@ function splitParagraphGroups(paragraph, refs) {
150
161
  }
151
162
  function renderParagraphPart(part) {
152
163
  if (part.kind === "raw") return part.xml;
153
- const open = openTag("w:r", part.mark?.attrs.rAttrs);
164
+ const open = openTagXml(wName("r"), rawAttrsOf(part.mark?.attrs.rAttrs));
154
165
  const rPr = part.mark?.attrs.rPr;
155
166
  return open + (typeof rPr === "string" ? rPr : "") + part.pieces.join("") + "</w:r>";
156
167
  }
@@ -196,13 +207,13 @@ function renderParagraphGroup(group, refs) {
196
207
  return `${prefix}<w:sdtContent>${body}</w:sdtContent></w:sdt>`;
197
208
  }
198
209
  function serializeParagraph(node, refs = NO_EXPORT_REFS) {
199
- const open = openTag("w:p", node.attrs.pAttrs);
210
+ const open = openTagXml(wName("p"), rawAttrsOf(node.attrs.pAttrs));
200
211
  const pPr = node.attrs.pPr;
201
212
  const body = splitParagraphGroups(node, refs).map((group) => renderParagraphGroup(group, refs)).join("");
202
213
  return open + (typeof pPr === "string" ? pPr : "") + body + "</w:p>";
203
214
  }
204
215
  export {
205
- openTag,
206
216
  preservedXml,
217
+ rawAttrsOf,
207
218
  serializeParagraph
208
219
  };
@@ -5,18 +5,22 @@ import {
5
5
  toTableWidth,
6
6
  widthNumber
7
7
  } from "../model/format.js";
8
+ import { elementXml, openTagXml } from "../ooxml/element.js";
8
9
  import { DocxExportError } from "../ooxml/errors.js";
10
+ import { wName } from "../ooxml/names.js";
9
11
  import { NO_EXPORT_REFS } from "./exportRefs.js";
10
12
  import {
13
+ innerXml,
11
14
  parseProps,
15
+ propsChild,
12
16
  renderProps,
13
17
  setPropsChild,
14
18
  TBL_PR_ORDER,
15
19
  TC_PR_ORDER
16
20
  } from "./propsXml.js";
17
21
  import {
18
- openTag,
19
22
  preservedXml,
23
+ rawAttrsOf,
20
24
  serializeParagraph
21
25
  } from "./serializeParagraph.js";
22
26
  function propsOf(xml, tag) {
@@ -27,15 +31,24 @@ function propsOf(xml, tag) {
27
31
  }
28
32
  return parsed;
29
33
  }
30
- function widthXml(name, width) {
31
- return `<w:${name} w:w="${widthNumber(width) ?? 0}" w:type="${width.type}"/>`;
34
+ function modelled(name, attrs, replacing) {
35
+ const inner = replacing === void 0 ? "" : innerXml(replacing);
36
+ return elementXml(wName(name), attrs, inner === "" ? [] : [inner]);
37
+ }
38
+ function widthXml(name, width, replacing) {
39
+ return modelled(
40
+ name,
41
+ [
42
+ [wName("w"), `${widthNumber(width) ?? 0}`],
43
+ [wName("type"), width.type]
44
+ ],
45
+ replacing
46
+ );
32
47
  }
33
48
  function withWidth(props, name, width, order) {
34
49
  if (!width) return props;
35
- return {
36
- ...props,
37
- children: setPropsChild(props.children, name, widthXml(name, width), order)
38
- };
50
+ const replacing = propsChild(props.children, name)?.xml;
51
+ return setPropsChild(props, name, widthXml(name, width, replacing), order);
39
52
  }
40
53
  function tablePropsXml(table) {
41
54
  const props = propsOf(table.attrs.tblPr, "w:tblPr");
@@ -45,26 +58,27 @@ function tablePropsXml(table) {
45
58
  function tableGridXml(table) {
46
59
  const gridCols = toGridCols(table.attrs.gridCols);
47
60
  if (gridCols.length === 0) return "";
48
- const cols = gridCols.map((w) => `<w:gridCol w:w="${w}"/>`).join("");
49
- return `<w:tblGrid>${cols}</w:tblGrid>`;
61
+ const cols = gridCols.map((w) => elementXml(wName("gridCol"), [[wName("w"), `${w}`]])).join("");
62
+ return elementXml(wName("tblGrid"), [], [cols]);
50
63
  }
51
64
  function cellPropsXml(cell, role) {
52
65
  const props = propsOf(cell.attrs.tcPr, "w:tcPr");
53
66
  const colspan = spanCount(cell.attrs.colspan);
54
67
  const rowspan = spanCount(cell.attrs.rowspan);
55
- const gridSpan = colspan > 1 ? `<w:gridSpan w:val="${colspan}"/>` : null;
56
- const vMerge = role === "continue" ? "<w:vMerge/>" : rowspan > 1 ? '<w:vMerge w:val="restart"/>' : null;
57
- let children = setPropsChild(
58
- props.children,
68
+ const gridSpan = colspan > 1 ? modelled(
59
69
  "gridSpan",
60
- gridSpan,
61
- TC_PR_ORDER
62
- );
63
- children = setPropsChild(children, "vMerge", vMerge, TC_PR_ORDER);
70
+ [[wName("val"), `${colspan}`]],
71
+ propsChild(props.children, "gridSpan")?.xml
72
+ ) : null;
73
+ const vMerge = role === "continue" ? elementXml(wName("vMerge"), []) : rowspan > 1 ? modelled(
74
+ "vMerge",
75
+ [[wName("val"), "restart"]],
76
+ propsChild(props.children, "vMerge")?.xml
77
+ ) : null;
78
+ const spanned = setPropsChild(props, "gridSpan", gridSpan, TC_PR_ORDER);
79
+ const merged = setPropsChild(spanned, "vMerge", vMerge, TC_PR_ORDER);
64
80
  const width = toTableWidth(cell.attrs.tcW);
65
- return renderProps(
66
- withWidth({ ...props, children }, "tcW", width, TC_PR_ORDER)
67
- );
81
+ return renderProps(withWidth(merged, "tcW", width, TC_PR_ORDER));
68
82
  }
69
83
  function cellBlockXml(block, refs) {
70
84
  if (block.type.name === "paragraph") return serializeParagraph(block, refs);
@@ -81,8 +95,8 @@ function wrapInSdt(xml, cell, role) {
81
95
  return `${prefix}<w:sdtContent>${xml}</w:sdtContent></w:sdt>`;
82
96
  }
83
97
  function cellXml(cell, role, refs) {
84
- const body = role === "continue" ? "<w:p/>" : cell.children.map((block) => cellBlockXml(block, refs)).join("");
85
- const xml = openTag("w:tc", cell.attrs.tcAttrs) + cellPropsXml(cell, role) + body + "</w:tc>";
98
+ const body = role === "continue" ? elementXml(wName("p"), []) : cell.children.map((block) => cellBlockXml(block, refs)).join("");
99
+ const xml = openTagXml(wName("tc"), rawAttrsOf(cell.attrs.tcAttrs)) + cellPropsXml(cell, role) + body + "</w:tc>";
86
100
  return wrapInSdt(xml, cell, role);
87
101
  }
88
102
  function placeRow(row, covering) {
@@ -116,7 +130,7 @@ function rowXml(row, covering, refs) {
116
130
  const cells = placeRow(row, covering).map((placed) => cellXml(placed.cell, placed.role, refs)).join("");
117
131
  const tblPrEx = row.attrs.tblPrEx;
118
132
  const trPr = row.attrs.trPr;
119
- return openTag("w:tr", row.attrs.trAttrs) + (typeof tblPrEx === "string" ? tblPrEx : "") + (typeof trPr === "string" ? trPr : "") + cells + "</w:tr>";
133
+ return openTagXml(wName("tr"), rawAttrsOf(row.attrs.trAttrs)) + (typeof tblPrEx === "string" ? tblPrEx : "") + (typeof trPr === "string" ? trPr : "") + cells + "</w:tr>";
120
134
  }
121
135
  function serializeTable(table, refs = NO_EXPORT_REFS) {
122
136
  const covering = [];
@@ -127,7 +141,7 @@ function serializeTable(table, refs = NO_EXPORT_REFS) {
127
141
  "a vertical merge in the table reaches past the last row"
128
142
  );
129
143
  }
130
- return openTag("w:tbl", table.attrs.tblAttrs) + tablePropsXml(table) + tableGridXml(table) + rows + "</w:tbl>";
144
+ return openTagXml(wName("tbl"), rawAttrsOf(table.attrs.tblAttrs)) + tablePropsXml(table) + tableGridXml(table) + rows + "</w:tbl>";
131
145
  }
132
146
  export {
133
147
  serializeTable