@portone/docx-editor 0.2.1 → 0.4.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 (253) hide show
  1. package/CHANGELOG.md +155 -0
  2. package/CONTRIBUTING.md +5 -1
  3. package/dist/DocxEditor.d.ts +6 -0
  4. package/dist/DocxEditor.js +13 -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 +59 -112
  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 +88 -74
  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 +45 -0
  33. package/dist/docx/formatting/attrs.js +86 -0
  34. package/dist/docx/formatting/context.d.ts +39 -0
  35. package/dist/docx/formatting/context.js +60 -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 +51 -0
  39. package/dist/docx/formatting/resolve.js +160 -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.d.ts +25 -2
  43. package/dist/docx/formatting/styles.js +87 -6
  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 +6 -13
  48. package/dist/docx/hyperlink.d.ts +14 -4
  49. package/dist/docx/hyperlink.js +4 -1
  50. package/dist/docx/identities.d.ts +67 -0
  51. package/dist/docx/identities.js +174 -0
  52. package/dist/docx/importDocx.d.ts +12 -1
  53. package/dist/docx/importDocx.js +90 -88
  54. package/dist/docx/importParagraph.d.ts +1 -1
  55. package/dist/docx/importParagraph.js +4 -3
  56. package/dist/docx/importTable.d.ts +2 -2
  57. package/dist/docx/importTable.js +69 -56
  58. package/dist/docx/invariants.d.ts +33 -0
  59. package/dist/docx/invariants.js +269 -0
  60. package/dist/docx/media.d.ts +6 -4
  61. package/dist/docx/media.js +6 -37
  62. package/dist/docx/newLists.d.ts +50 -0
  63. package/dist/docx/newLists.js +59 -0
  64. package/dist/docx/notes.js +3 -7
  65. package/dist/docx/numberingPlanner.d.ts +11 -0
  66. package/dist/docx/numberingPlanner.js +50 -0
  67. package/dist/docx/packageParts.d.ts +42 -0
  68. package/dist/docx/packageParts.js +135 -0
  69. package/dist/docx/pageGeometry.d.ts +2 -0
  70. package/dist/docx/pageGeometry.js +18 -9
  71. package/dist/docx/paraProps.d.ts +10 -22
  72. package/dist/docx/paraProps.js +47 -76
  73. package/dist/docx/partPlan.d.ts +36 -0
  74. package/dist/docx/partPlan.js +59 -0
  75. package/dist/docx/protectionPolicy.d.ts +127 -0
  76. package/dist/docx/protectionPolicy.js +169 -0
  77. package/dist/docx/relationships.d.ts +1 -1
  78. package/dist/docx/relationships.js +8 -13
  79. package/dist/docx/runProps.d.ts +9 -22
  80. package/dist/docx/runProps.js +15 -168
  81. package/dist/docx/scan.js +20 -52
  82. package/dist/docx/sdt.js +9 -34
  83. package/dist/docx/sdtProps.d.ts +8 -1
  84. package/dist/docx/sdtProps.js +10 -0
  85. package/dist/docx/serializeBlock.d.ts +2 -0
  86. package/dist/docx/serializeBlock.js +8 -5
  87. package/dist/docx/serializeTable.js +32 -15
  88. package/dist/docx/session.d.ts +42 -14
  89. package/dist/docx/session.js +53 -17
  90. package/dist/docx/tableFormatting/conditions.d.ts +76 -0
  91. package/dist/docx/tableFormatting/conditions.js +196 -0
  92. package/dist/docx/tableFormatting/editing.d.ts +7 -3
  93. package/dist/docx/tableFormatting/editing.js +111 -143
  94. package/dist/docx/tableFormatting/reading.d.ts +63 -23
  95. package/dist/docx/tableFormatting/reading.js +132 -38
  96. package/dist/docx/tableFormatting.d.ts +1 -0
  97. package/dist/docx/tableFormatting.js +1 -0
  98. package/dist/docx/tableTemplate.js +30 -19
  99. package/dist/download.d.ts +8 -5
  100. package/dist/download.js +2 -0
  101. package/dist/editor/clipboard/inlineFormatting.js +19 -30
  102. package/dist/editor/commands/comments/editing.d.ts +6 -2
  103. package/dist/editor/commands/comments/editing.js +19 -31
  104. package/dist/editor/commands/exportQueries.d.ts +15 -0
  105. package/dist/editor/commands/exportQueries.js +14 -0
  106. package/dist/editor/commands/fidelityQueries.d.ts +12 -0
  107. package/dist/editor/commands/fidelityQueries.js +8 -0
  108. package/dist/editor/commands/formatting/editing.d.ts +2 -2
  109. package/dist/editor/commands/formatting/editing.js +26 -108
  110. package/dist/editor/commands/formatting/propertyCommands.d.ts +21 -0
  111. package/dist/editor/commands/formatting/propertyCommands.js +108 -0
  112. package/dist/editor/commands/formatting/shared.d.ts +3 -3
  113. package/dist/editor/commands/formatting/shared.js +5 -2
  114. package/dist/editor/commands/indentCommands.js +5 -4
  115. package/dist/editor/commands/index.d.ts +11 -0
  116. package/dist/editor/commands/index.js +5 -0
  117. package/dist/editor/commands/linkCommands.js +5 -6
  118. package/dist/editor/commands/listCommands.d.ts +6 -3
  119. package/dist/editor/commands/listCommands.js +73 -30
  120. package/dist/editor/commands/lockCommands.d.ts +7 -1
  121. package/dist/editor/commands/lockCommands.js +35 -46
  122. package/dist/editor/commands/paragraphCommands.js +33 -31
  123. package/dist/editor/commands/spacingCommands.js +1 -1
  124. package/dist/editor/createEditor.d.ts +17 -29
  125. package/dist/editor/createEditor.js +58 -58
  126. package/dist/editor/documentStyles.d.ts +11 -35
  127. package/dist/editor/documentStyles.js +9 -52
  128. package/dist/editor/editorDocument.d.ts +57 -0
  129. package/dist/editor/editorDocument.js +86 -0
  130. package/dist/editor/externalClipboard.d.ts +13 -2
  131. package/dist/editor/externalClipboard.js +76 -75
  132. package/dist/editor/insertTable.js +4 -3
  133. package/dist/editor/paragraphEdits.d.ts +27 -19
  134. package/dist/editor/paragraphEdits.js +18 -19
  135. package/dist/editor/paragraphPlacement.d.ts +11 -0
  136. package/dist/editor/paragraphPlacement.js +18 -0
  137. package/dist/editor/plugins/displayDerivation.d.ts +47 -0
  138. package/dist/editor/plugins/displayDerivation.js +78 -0
  139. package/dist/editor/plugins/imagePaste.js +11 -7
  140. package/dist/editor/plugins/keymap.js +18 -3
  141. package/dist/editor/plugins/numberingDecorations.d.ts +14 -14
  142. package/dist/editor/plugins/numberingDecorations.js +50 -19
  143. package/dist/editor/plugins/paragraphDisplay.d.ts +7 -0
  144. package/dist/editor/plugins/paragraphDisplay.js +57 -0
  145. package/dist/editor/plugins/tabLayout.js +1 -1
  146. package/dist/editor/plugins/tableDisplay.d.ts +6 -0
  147. package/dist/editor/plugins/tableDisplay.js +30 -0
  148. package/dist/index.d.ts +2 -0
  149. package/dist/model/format.d.ts +80 -8
  150. package/dist/model/format.js +100 -22
  151. package/dist/model/tabStops.d.ts +9 -0
  152. package/dist/model/tabStops.js +18 -0
  153. package/dist/numbering/listRegistry.d.ts +37 -0
  154. package/dist/numbering/listRegistry.js +112 -0
  155. package/dist/numbering/listTemplate.d.ts +26 -17
  156. package/dist/numbering/listTemplate.js +88 -25
  157. package/dist/numbering/markers.d.ts +15 -6
  158. package/dist/numbering/markers.js +26 -59
  159. package/dist/numbering/parseNumbering.d.ts +84 -6
  160. package/dist/numbering/parseNumbering.js +131 -44
  161. package/dist/numbering/spellers.d.ts +15 -0
  162. package/dist/numbering/spellers.js +96 -0
  163. package/dist/numbering/writeNumbering.d.ts +8 -7
  164. package/dist/numbering/writeNumbering.js +28 -39
  165. package/dist/ooxml/childOrder.d.ts +34 -0
  166. package/dist/ooxml/childOrder.js +496 -0
  167. package/dist/ooxml/conformance.d.ts +28 -0
  168. package/dist/ooxml/conformance.js +20 -0
  169. package/dist/ooxml/element.d.ts +24 -11
  170. package/dist/ooxml/element.js +31 -12
  171. package/dist/ooxml/errors.d.ts +6 -5
  172. package/dist/ooxml/image.d.ts +4 -0
  173. package/dist/ooxml/image.js +2 -5
  174. package/dist/ooxml/names.d.ts +9 -0
  175. package/dist/ooxml/names.js +8 -1
  176. package/dist/ooxml/partSplice.d.ts +67 -0
  177. package/dist/ooxml/partSplice.js +176 -0
  178. package/dist/ooxml/props.d.ts +112 -0
  179. package/dist/{docx/propsXml.js → ooxml/props.js} +72 -200
  180. package/dist/ooxml/simpleTypes.d.ts +103 -0
  181. package/dist/ooxml/simpleTypes.js +182 -0
  182. package/dist/ooxml/tabStops.js +8 -27
  183. package/dist/ooxml/tagScan.d.ts +34 -0
  184. package/dist/ooxml/tagScan.js +108 -0
  185. package/dist/ooxml/units.d.ts +25 -13
  186. package/dist/ooxml/units.js +65 -31
  187. package/dist/ooxml/xml.d.ts +35 -15
  188. package/dist/ooxml/xml.js +43 -11
  189. package/dist/page/PageGuides.d.ts +1 -1
  190. package/dist/page/PageGuides.js +0 -13
  191. package/dist/page/blockKinds.d.ts +61 -0
  192. package/dist/page/blockKinds.js +11 -0
  193. package/dist/page/kinds/index.d.ts +6 -0
  194. package/dist/page/kinds/index.js +10 -0
  195. package/dist/page/kinds/paragraphKind.d.ts +10 -0
  196. package/dist/page/kinds/paragraphKind.js +71 -0
  197. package/dist/page/kinds/tableKind.d.ts +10 -0
  198. package/dist/page/kinds/tableKind.js +178 -0
  199. package/dist/page/measureBlocks.d.ts +3 -14
  200. package/dist/page/measureBlocks.js +24 -45
  201. package/dist/page/pageDecorations.d.ts +27 -44
  202. package/dist/page/pageDecorations.js +70 -152
  203. package/dist/page/pageLayout.d.ts +2 -29
  204. package/dist/page/pageLayout.js +39 -16
  205. package/dist/page/usePageLayout.d.ts +2 -24
  206. package/dist/page/usePageLayout.js +5 -48
  207. package/dist/schema/attrRoles.d.ts +25 -18
  208. package/dist/schema/attrRoles.js +110 -68
  209. package/dist/schema/displayDerivation.d.ts +82 -0
  210. package/dist/schema/displayDerivation.js +130 -0
  211. package/dist/schema/docxSchema.d.ts +3 -0
  212. package/dist/schema/docxSchema.js +58 -17
  213. package/dist/schema/editGuard.d.ts +1 -1
  214. package/dist/schema/guards.d.ts +37 -14
  215. package/dist/schema/guards.js +29 -3
  216. package/dist/schema/index.d.ts +2 -0
  217. package/dist/schema/index.js +2 -0
  218. package/dist/schema/locks.d.ts +11 -44
  219. package/dist/schema/locks.js +0 -9
  220. package/dist/schema/preservedGuards.d.ts +26 -6
  221. package/dist/schema/preservedGuards.js +31 -1
  222. package/dist/schema/protection.d.ts +7 -1
  223. package/dist/schema/protection.js +2 -1
  224. package/dist/schema/sourceEquality.d.ts +1 -9
  225. package/dist/schema/sourceEquality.js +1 -27
  226. package/dist/styles/classNames.d.ts +13 -2
  227. package/dist/styles/classNames.js +13 -2
  228. package/dist/styles/inlineStyle.js +15 -6
  229. package/dist/styles.css +16 -18
  230. package/dist/table/cellFormatting.d.ts +8 -1
  231. package/dist/table/cellFormatting.js +11 -20
  232. package/dist/table/commands.js +20 -18
  233. package/dist/table/format.d.ts +3 -0
  234. package/dist/table/format.js +3 -9
  235. package/dist/table/gridBorders.d.ts +24 -30
  236. package/dist/table/gridBorders.js +35 -59
  237. package/dist/table/merge.d.ts +2 -6
  238. package/dist/table/merge.js +5 -5
  239. package/package.json +7 -3
  240. package/dist/docx/comments/contentTypes.d.ts +0 -7
  241. package/dist/docx/comments/contentTypes.js +0 -38
  242. package/dist/docx/comments/verifying.js +0 -206
  243. package/dist/docx/formatting/effectiveParagraph.d.ts +0 -15
  244. package/dist/docx/formatting/effectiveParagraph.js +0 -81
  245. package/dist/docx/propsXml.d.ts +0 -67
  246. package/dist/docx/uniqueControls.d.ts +0 -14
  247. package/dist/docx/uniqueControls.js +0 -62
  248. package/dist/editor/plugins/commentReservations.d.ts +0 -5
  249. package/dist/editor/plugins/commentReservations.js +0 -26
  250. package/dist/editor/plugins/styledParagraphs.d.ts +0 -15
  251. package/dist/editor/plugins/styledParagraphs.js +0 -65
  252. package/dist/page/tableMeasurements.d.ts +0 -18
  253. package/dist/page/tableMeasurements.js +0 -119
@@ -45,9 +45,8 @@ export declare function relIdIn(prefix: string): string | null;
45
45
  *
46
46
  * A tag that already names one has only that value rewritten, so a link nobody retargeted comes
47
47
  * out as the very string it went in as. A tag that named none is given the attribute, which the
48
- * specification has supersede any `w:anchor` beside it, along with the namespace it lives in: the
49
- * body we write into is not always one that declares it, and declaring it again where it is
50
- * already bound to the same namespace changes nothing.
48
+ * specification has supersede any `w:anchor` beside it. The namespace it lives in is the part
49
+ * root's to bind, which `hyperlinkRefs` reports and `docx/exportDocx` sees to.
51
50
  */
52
51
  export declare function withRelId(prefix: string, relId: string): string;
53
52
  /** Which relationship each address goes out on while the body is being written */
@@ -62,6 +61,17 @@ export interface LinkRefs {
62
61
  }
63
62
  /** What a serializer called on its own, outside an export, has to work with */
64
63
  export declare const NO_LINK_REFS: LinkRefs;
64
+ /**
65
+ * The refs a body is written through, and what the part it went into has to declare afterwards.
66
+ *
67
+ * `withRelId` puts the attribute on exactly the tag that arrived without one, and that is the tag
68
+ * `relIdOf` is handed a null `was` for, so what is recorded here and what is written cannot come
69
+ * apart.
70
+ */
71
+ export interface LinkWriting extends LinkRefs {
72
+ /** Whether a link went out under an `r:id` its own opening tag did not arrive carrying */
73
+ addedRelId(): boolean;
74
+ }
65
75
  /**
66
76
  * Hands out the relationship for every address the body points at, adding one where the package
67
77
  * has none.
@@ -72,4 +82,4 @@ export declare const NO_LINK_REFS: LinkRefs;
72
82
  * relationship a link arrived on is kept wherever it still leads where the link now goes, so
73
83
  * retargeting one link in a document leaves every other link's XML alone.
74
84
  */
75
- export declare function hyperlinkRefs(relationships: RelationshipWriter): LinkRefs;
85
+ export declare function hyperlinkRefs(relationships: RelationshipWriter): LinkWriting;
@@ -34,7 +34,7 @@ function withRelId(prefix, relId) {
34
34
  return prefix.replace(REL_ID_VALUE, ` ${REL_ID_ATTR}="${relId}"`);
35
35
  }
36
36
  const name = "<w:hyperlink";
37
- return `${name} xmlns:r="${R_NS}" ${REL_ID_ATTR}="${relId}"${prefix.slice(name.length)}`;
37
+ return `${name} ${REL_ID_ATTR}="${relId}"${prefix.slice(name.length)}`;
38
38
  }
39
39
  var NO_LINK_REFS = { relIdOf: () => void 0 };
40
40
  function hyperlinkRefs(relationships) {
@@ -42,8 +42,11 @@ function hyperlinkRefs(relationships) {
42
42
  (rel) => rel.type === HYPERLINK_REL_TYPE && rel.external
43
43
  );
44
44
  const added = /* @__PURE__ */ new Map();
45
+ let attributeAdded = false;
45
46
  return {
47
+ addedRelId: () => attributeAdded,
46
48
  relIdOf: (href, was) => {
49
+ if (was === null) attributeAdded = true;
47
50
  const kept = external.find((rel) => rel.id === was);
48
51
  if (kept?.target === href) return was ?? void 0;
49
52
  const found = external.find((rel) => rel.target === href);
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Settling, just before export, every name that may be held by one node only.
3
+ *
4
+ * An edit can leave one name standing in two places: splitting a paragraph inside a content
5
+ * control leaves that control in both halves, and dropping a copy of a paragraph carries the
6
+ * original's identifiers along with it. Each name a document must keep unique is one rule here,
7
+ * and the pass walks the blocks once in document order, so the first node to claim a name keeps it
8
+ * and every later claimant gives it up.
9
+ *
10
+ * A document nobody broke apart comes back node for node as it was, which is what keeps an
11
+ * unedited block going out as its original XML.
12
+ *
13
+ * The one walk answers two callers: `withUniqueIdentities` hands the writer the settled document
14
+ * and throws over a block that cannot yield, and `identityProblems` runs the same walk for
15
+ * `docx/invariants` and lists those blocks instead, so what the query reports and what the write
16
+ * refuses cannot come apart.
17
+ */
18
+ import { type Node as PMNode } from "prosemirror-model";
19
+ import { type DocxExportErrorCode } from "../ooxml/errors";
20
+ export interface IdentityRule {
21
+ readonly name: string;
22
+ /**
23
+ * The block to write in place of `block`, with every name it claims that `written` already holds
24
+ * given up, and the names it keeps added to `written`. Returns `block` itself when nothing
25
+ * changes. null means the block cannot yield and the export must refuse. A block's children are
26
+ * not visited here; the walker visits them after this call.
27
+ */
28
+ visit(block: PMNode, written: Set<string>): PMNode | null;
29
+ }
30
+ /**
31
+ * The block a node was opened from, which `docx/exportDocx` writes the original bytes back for.
32
+ * A second node claiming the same block is written from its own attrs instead, and a block that
33
+ * exists only as its original XML has nothing to be written from, so it cannot yield.
34
+ */
35
+ export declare const sourceBlockRule: IdentityRule;
36
+ /**
37
+ * A later paragraph carrying an identifier already written goes out without one, and without its
38
+ * `w14:textId` too, which may not stand without a `w14:paraId` beside it. The walker reaches the
39
+ * paragraphs inside a table, so the rule reads the one block it is handed.
40
+ */
41
+ export declare const paragraphIdRule: IdentityRule;
42
+ /**
43
+ * A content control cannot cross a paragraph, so splitting a paragraph in the middle of one leaves
44
+ * that same control standing in two, and dropping unmarked text into the middle of one breaks it
45
+ * in two within the paragraph. Each piece after the first opens as a copy with a `w:id` of its
46
+ * own (see `docx/sdt` for what a copy must not carry along).
47
+ */
48
+ export declare const controlRule: IdentityRule;
49
+ /**
50
+ * In the order they apply to one block: the source rule first, so that a second claimant of a
51
+ * block is already a rebuilt paragraph by the time the paragraph rule takes its identifier away.
52
+ */
53
+ export declare const IDENTITY_RULES: readonly IdentityRule[];
54
+ /** A block that cannot yield: what the export refuses it with, and where it stands in the document */
55
+ export interface IdentityProblem {
56
+ readonly code: DocxExportErrorCode;
57
+ readonly message: string;
58
+ readonly pos: number;
59
+ }
60
+ /** The document with every later claimant of a name released, in document order */
61
+ export declare function withUniqueIdentities(doc: PMNode, rules?: readonly IdentityRule[]): PMNode;
62
+ /**
63
+ * Every block `withUniqueIdentities` would refuse over, in document order, each where it stands;
64
+ * empty when the pass would go through. The pass is run as the export runs it and its settled
65
+ * document is dropped, so a caller asking ahead of the write reads the same refusal it would throw.
66
+ */
67
+ export declare function identityProblems(doc: PMNode, rules?: readonly IdentityRule[]): readonly IdentityProblem[];
@@ -0,0 +1,174 @@
1
+ // src/docx/identities.ts
2
+ import { Fragment } from "prosemirror-model";
3
+ import { DocxExportError } from "../ooxml/errors.js";
4
+ import { qualify } from "../ooxml/names.js";
5
+ import { parseAttrs } from "../ooxml/tagScan.js";
6
+ import { docxSchema } from "../schema/index.js";
7
+ import { withoutParagraphIds } from "./cloning.js";
8
+ import { copiedControlPrefix, newControlId } from "./sdt.js";
9
+ var sourceBlockRule = {
10
+ name: "sourceBlock",
11
+ visit(block, written) {
12
+ const srcId = block.attrs.srcId;
13
+ if (typeof srcId !== "string" && typeof srcId !== "number") return block;
14
+ const name = String(srcId);
15
+ if (!written.has(name)) {
16
+ written.add(name);
17
+ return block;
18
+ }
19
+ if (block.type.isInGroup("preserved")) return null;
20
+ return block.type.create(
21
+ { ...block.attrs, srcId: null },
22
+ block.content,
23
+ block.marks
24
+ );
25
+ }
26
+ };
27
+ var PARA_ID = qualify("w14", "paraId");
28
+ var LONG_HEX = /^[0-9A-Fa-f]{8}$/;
29
+ function paragraphIdOf(pAttrs) {
30
+ if (typeof pAttrs !== "string") return null;
31
+ const value = parseAttrs(pAttrs)?.find(([name]) => name === PARA_ID)?.[1];
32
+ if (value === void 0 || !LONG_HEX.test(value)) return null;
33
+ const id = Number.parseInt(value, 16);
34
+ return id > 0 && id < 2147483648 ? id : null;
35
+ }
36
+ var paragraphIdRule = {
37
+ name: "paragraphId",
38
+ visit(block, written) {
39
+ if (block.type !== docxSchema.nodes.paragraph) return block;
40
+ const id = paragraphIdOf(block.attrs.pAttrs);
41
+ if (id === null) return block;
42
+ const name = String(id);
43
+ if (!written.has(name)) {
44
+ written.add(name);
45
+ return block;
46
+ }
47
+ return block.type.create(
48
+ { ...block.attrs, pAttrs: withoutParagraphIds(block.attrs.pAttrs) },
49
+ block.content,
50
+ block.marks
51
+ );
52
+ }
53
+ };
54
+ function sdtMarkOf(node) {
55
+ return node.marks.find((mark) => mark.type === docxSchema.marks.sdt) ?? null;
56
+ }
57
+ function controlName(mark) {
58
+ const key = mark.attrs.sdtKey;
59
+ const prefix = mark.attrs.sdtPrefix;
60
+ return `${typeof key === "number" ? key : 0} ${typeof prefix === "string" ? prefix : ""}`;
61
+ }
62
+ function copiedMark(mark) {
63
+ const prefix = mark.attrs.sdtPrefix;
64
+ if (typeof prefix !== "string") return mark;
65
+ return mark.type.create({
66
+ ...mark.attrs,
67
+ sdtPrefix: copiedControlPrefix(prefix, newControlId())
68
+ });
69
+ }
70
+ function claim(mark, written) {
71
+ const name = controlName(mark);
72
+ const copy = written.has(name) ? copiedMark(mark) : null;
73
+ written.add(name);
74
+ return { mark, copy };
75
+ }
76
+ function rewriteParagraph(paragraph, written) {
77
+ const inline = [];
78
+ let running = null;
79
+ let renamed = false;
80
+ paragraph.forEach((child) => {
81
+ const mark = sdtMarkOf(child);
82
+ if (!mark) {
83
+ running = null;
84
+ inline.push(child);
85
+ return;
86
+ }
87
+ const control = running?.mark.eq(mark) ? running : claim(mark, written);
88
+ running = control;
89
+ if (!control.copy) {
90
+ inline.push(child);
91
+ return;
92
+ }
93
+ renamed = true;
94
+ inline.push(
95
+ child.mark(control.copy.addToSet(mark.removeFromSet(child.marks)))
96
+ );
97
+ });
98
+ return renamed ? paragraph.copy(Fragment.fromArray(inline)) : paragraph;
99
+ }
100
+ var controlRule = {
101
+ name: "control",
102
+ visit(block, written) {
103
+ return block.type === docxSchema.nodes.paragraph ? rewriteParagraph(block, written) : block;
104
+ }
105
+ };
106
+ var IDENTITY_RULES = [
107
+ sourceBlockRule,
108
+ paragraphIdRule,
109
+ controlRule
110
+ ];
111
+ function refusalOf(block, pos) {
112
+ return {
113
+ code: "unsupported-content",
114
+ message: `a preserved block stands in two places (${block.type.name})`,
115
+ pos
116
+ };
117
+ }
118
+ function visitBlock(block, pos, walk) {
119
+ let current = block;
120
+ for (const [index, rule] of walk.rules.entries()) {
121
+ const next = rule.visit(current, walk.written[index]);
122
+ if (next === null) {
123
+ walk.refuse(refusalOf(block, pos));
124
+ return current;
125
+ }
126
+ current = next;
127
+ }
128
+ return current;
129
+ }
130
+ function rewriteBlock(block, pos, walk) {
131
+ const visited = visitBlock(block, pos, walk);
132
+ if (visited.inlineContent || visited.childCount === 0) return visited;
133
+ const children = [];
134
+ let changed = false;
135
+ visited.forEach((child, offset) => {
136
+ const next = rewriteBlock(child, pos + 1 + offset, walk);
137
+ changed ||= next !== child;
138
+ children.push(next);
139
+ });
140
+ return changed ? visited.copy(Fragment.fromArray(children)) : visited;
141
+ }
142
+ function walkBlocks(doc, rules, refuse) {
143
+ const walk = {
144
+ rules,
145
+ written: rules.map(() => /* @__PURE__ */ new Set()),
146
+ refuse
147
+ };
148
+ const blocks = [];
149
+ let changed = false;
150
+ doc.forEach((block, offset) => {
151
+ const next = rewriteBlock(block, offset, walk);
152
+ changed ||= next !== block;
153
+ blocks.push(next);
154
+ });
155
+ return changed ? doc.copy(Fragment.fromArray(blocks)) : doc;
156
+ }
157
+ function withUniqueIdentities(doc, rules = IDENTITY_RULES) {
158
+ return walkBlocks(doc, rules, (problem) => {
159
+ throw new DocxExportError(problem.code, problem.message);
160
+ });
161
+ }
162
+ function identityProblems(doc, rules = IDENTITY_RULES) {
163
+ const problems = [];
164
+ walkBlocks(doc, rules, (problem) => problems.push(problem));
165
+ return problems;
166
+ }
167
+ export {
168
+ IDENTITY_RULES,
169
+ controlRule,
170
+ identityProblems,
171
+ paragraphIdRule,
172
+ sourceBlockRule,
173
+ withUniqueIdentities
174
+ };
@@ -4,6 +4,8 @@
4
4
  * This module knows nothing about the screen. All it knows is the file and the document model.
5
5
  */
6
6
  import { type Node as PMNode } from "prosemirror-model";
7
+ import { type XmlParser } from "../ooxml/xml";
8
+ import { type FidelityNote } from "./fidelity";
7
9
  import { SessionStore } from "./session";
8
10
  /** A docx file as bytes, however it arrived (file input, fetch, filesystem) */
9
11
  export type DocxBytes = ArrayBuffer | Uint8Array;
@@ -15,7 +17,16 @@ export type DocxBytes = ArrayBuffer | Uint8Array;
15
17
  * engine on `./core` stays synchronous and bytes-only.
16
18
  */
17
19
  export type DocxSource = DocxBytes | Blob;
18
- export declare function importDocx(input: DocxBytes): {
20
+ /** What a caller may say about a read beyond handing over the bytes */
21
+ export interface ImportOptions {
22
+ /**
23
+ * The parser every part of the package is read through. Left out, the `DOMParser` global is
24
+ * used, and a runtime carrying none refuses the read with `no-xml-parser`.
25
+ */
26
+ xmlParser?: XmlParser;
27
+ }
28
+ export declare function importDocx(input: DocxBytes, options?: ImportOptions): {
19
29
  doc: PMNode;
20
30
  session: SessionStore;
31
+ notes: FidelityNote[];
21
32
  };
@@ -1,7 +1,10 @@
1
1
  // src/docx/importDocx.ts
2
2
  import { Fragment } from "prosemirror-model";
3
- import { toRunFormat } from "../model/format.js";
4
- import { parseNumbering } from "../numbering/parseNumbering.js";
3
+ import {
4
+ bindsWritingPrefix,
5
+ conformanceOf,
6
+ STRICT_OFFICE_DOCUMENT_REL
7
+ } from "../ooxml/conformance.js";
5
8
  import { DocxImportError } from "../ooxml/errors.js";
6
9
  import {
7
10
  childByLocalName,
@@ -9,54 +12,59 @@ import {
9
12
  elementChildren,
10
13
  localPart,
11
14
  parseXml,
12
- R_NS
15
+ R_NS,
16
+ withXmlParser
13
17
  } from "../ooxml/xml.js";
14
18
  import { docxSchema } from "../schema/index.js";
15
19
  import { commentReferencesIn, readComments } from "./comments.js";
16
20
  import { openParts } from "./container.js";
17
- import { DEFAULT_TAB_STOP_PT, readDefaultTabStop } from "./documentSettings.js";
18
21
  import {
19
- defaultParagraphStyleIdOf,
20
- defaultTableStyleIdOf,
21
- effectiveParagraphFormat,
22
- effectiveParagraphStyle,
23
- layerRunFormat,
22
+ DEFAULT_TAB_STOP_PT,
23
+ readCompatSettings,
24
+ readDefaultTabStop
25
+ } from "./documentSettings.js";
26
+ import { fidelityNotesOf } from "./fidelity.js";
27
+ import {
28
+ formattingContextOf,
24
29
  NO_DOCUMENT_DEFAULTS,
25
- NO_STYLES,
26
- readDefaultParagraphFormat,
27
30
  readDocumentDefaults,
28
31
  readParagraphStyles,
29
- readStyles
32
+ styledParagraph
30
33
  } from "./formatting.js";
31
34
  import { readHeadersFooters } from "./headersFooters.js";
32
35
  import { readLinkTargets } from "./hyperlink.js";
33
36
  import { buildParagraph } from "./importParagraph.js";
34
37
  import { buildTable } from "./importTable.js";
35
38
  import { readImageSources } from "./media.js";
39
+ import { NUMBERING_REL_TYPE } from "./newLists.js";
36
40
  import { readNotes } from "./notes.js";
41
+ import { readPart, relatedPartPath } from "./packageParts.js";
37
42
  import { readBodyGeometry } from "./pageGeometry.js";
38
- import { resolveTarget } from "./relationships.js";
43
+ import { readRelationships } from "./relationships.js";
39
44
  import { scanBody } from "./scan.js";
40
- import { SessionStore } from "./session.js";
45
+ import {
46
+ BODY_STORY_KEY,
47
+ blockKey,
48
+ newSessionId,
49
+ SessionStore
50
+ } from "./session.js";
41
51
  import { NO_THEME_FONTS, readThemeFonts } from "./theme.js";
42
52
  var OFFICE_DOCUMENT_REL = `${R_NS}/officeDocument`;
43
53
  var STYLES_REL = `${R_NS}/styles`;
44
- var NUMBERING_REL = `${R_NS}/numbering`;
45
54
  var THEME_REL = `${R_NS}/theme`;
46
55
  var SETTINGS_REL = `${R_NS}/settings`;
47
- function relationshipTarget(parts, relsPath, type) {
48
- const rels = parts.get(relsPath);
49
- if (!rels) return null;
50
- for (const rel of elementChildren(
51
- parseXml(decodeUtf8(rels).text).documentElement
52
- )) {
53
- if (rel.getAttribute("Type") === type) return rel.getAttribute("Target");
54
- }
55
- return null;
56
- }
57
56
  function findMainPartPath(parts) {
58
- const target = relationshipTarget(parts, "_rels/.rels", OFFICE_DOCUMENT_REL);
57
+ const relationships = readRelationships(parts, "_rels/.rels");
58
+ const target = relationships.find(
59
+ (rel) => rel.type === OFFICE_DOCUMENT_REL
60
+ )?.target;
59
61
  if (!target) {
62
+ if (relationships.some((rel) => rel.type === STRICT_OFFICE_DOCUMENT_REL)) {
63
+ throw new DocxImportError(
64
+ "unsupported-conformance",
65
+ "the package relates its main document part as an ECMA-376 Strict one"
66
+ );
67
+ }
60
68
  throw new DocxImportError(
61
69
  "missing-part",
62
70
  "no relationship pointing at the main document part"
@@ -64,22 +72,27 @@ function findMainPartPath(parts) {
64
72
  }
65
73
  return target.replace(/^\//, "");
66
74
  }
67
- function relatedPartPath(parts, mainPartPath, type) {
68
- const directory = mainPartPath.replace(/[^/]+$/, "");
69
- const fileName = mainPartPath.slice(directory.length);
70
- const target = relationshipTarget(
71
- parts,
72
- `${directory}_rels/${fileName}.rels`,
73
- type
74
- );
75
- if (!target) return null;
76
- return resolveTarget(mainPartPath, target);
77
- }
78
- function readPart(parts, path) {
79
- const bytes = path === null ? void 0 : parts.get(path);
80
- return bytes ? decodeUtf8(bytes).text : null;
75
+ function assertWritableMainPart(root) {
76
+ if (conformanceOf(root) === "strict") {
77
+ throw new DocxImportError(
78
+ "unsupported-conformance",
79
+ "the main document part is written in the ECMA-376 Strict vocabulary"
80
+ );
81
+ }
82
+ if (conformanceOf(root) !== "transitional" || root.localName !== "document") {
83
+ throw new DocxImportError(
84
+ "unsupported-content",
85
+ "the main part is not a Transitional WordprocessingML document"
86
+ );
87
+ }
88
+ if (!bindsWritingPrefix(root)) {
89
+ throw new DocxImportError(
90
+ "unsupported-content",
91
+ "the main document part binds WordprocessingML to a prefix other than w"
92
+ );
93
+ }
81
94
  }
82
- function buildBlock(el, srcId, sources, styles, defaultTableStyleId) {
95
+ function buildBlock(el, srcId, sources, context) {
83
96
  if (el.localName === "bookmarkStart" || el.localName === "bookmarkEnd") {
84
97
  return docxSchema.nodes.bookmarkBlock.create({
85
98
  srcId,
@@ -91,36 +104,18 @@ function buildBlock(el, srcId, sources, styles, defaultTableStyleId) {
91
104
  if (paragraph) return paragraph;
92
105
  }
93
106
  if (el.localName === "tbl") {
94
- const table = buildTable(el, srcId, sources, styles, defaultTableStyleId);
107
+ const table = buildTable(el, srcId, sources, context);
95
108
  if (table) return table;
96
109
  }
97
110
  return docxSchema.nodes.docxRaw.create({ srcId, name: el.nodeName });
98
111
  }
99
- function styledInline(node, style) {
100
- const mark = node.marks.find((entry) => entry.type === docxSchema.marks.run);
101
- if (!mark) return node;
102
- const format = layerRunFormat(style, toRunFormat(mark.attrs.format));
103
- const next = mark.type.create({ ...mark.attrs, format });
104
- return node.mark(next.addToSet(node.marks));
105
- }
106
- function styledParagraph(node, context) {
107
- const style = effectiveParagraphStyle(node.attrs.pPr, context);
108
- const inline = style ? node.children.map((child) => styledInline(child, style.run)) : node.children;
109
- return node.type.create(
110
- {
111
- ...node.attrs,
112
- format: effectiveParagraphFormat(node.attrs.pPr, context),
113
- styleRun: style ? layerRunFormat(style.run, null) : null
114
- },
115
- Fragment.fromArray(inline),
116
- node.marks
117
- );
118
- }
119
112
  function withStyleFormats(node, context) {
120
113
  if (node.type === docxSchema.nodes.paragraph) {
121
114
  return styledParagraph(node, context);
122
115
  }
123
- if (node.childCount === 0) return node;
116
+ if (node.childCount === 0 || node.type === docxSchema.nodes.table) {
117
+ return node;
118
+ }
124
119
  const children = node.children.map(
125
120
  (child) => withStyleFormats(child, context)
126
121
  );
@@ -153,7 +148,10 @@ function foldTrailingSectPr(scan) {
153
148
  suffix: last.xml + scan.suffix
154
149
  };
155
150
  }
156
- function importDocx(input) {
151
+ function importDocx(input, options) {
152
+ return withXmlParser(options?.xmlParser, () => readDocx(input));
153
+ }
154
+ function readDocx(input) {
157
155
  const bytes = input instanceof Uint8Array ? input : new Uint8Array(input);
158
156
  const parts = openParts(bytes);
159
157
  const mainPartPath = findMainPartPath(parts);
@@ -164,6 +162,7 @@ function importDocx(input) {
164
162
  const { text: source, hadBom } = decodeUtf8(mainPart);
165
163
  const scanned = scanBody(source);
166
164
  const dom = parseXml(source);
165
+ assertWritableMainPart(dom.documentElement);
167
166
  const body = childByLocalName(dom.documentElement, "body");
168
167
  if (!body) {
169
168
  throw new DocxImportError("missing-body", "document has no w:body");
@@ -182,25 +181,25 @@ function importDocx(input) {
182
181
  parts,
183
182
  relatedPartPath(parts, mainPartPath, SETTINGS_REL)
184
183
  );
185
- const defaultTabStopPt = readDefaultTabStop(settingsXml === null ? null : parseXml(settingsXml)) ?? DEFAULT_TAB_STOP_PT;
186
- const numberingPartPath = relatedPartPath(parts, mainPartPath, NUMBERING_REL);
184
+ const settingsDom = settingsXml === null ? null : parseXml(settingsXml);
185
+ const defaultTabStopPt = readDefaultTabStop(settingsDom) ?? DEFAULT_TAB_STOP_PT;
186
+ const numberingPartPath = relatedPartPath(
187
+ parts,
188
+ mainPartPath,
189
+ NUMBERING_REL_TYPE
190
+ );
187
191
  const themeXml = readPart(
188
192
  parts,
189
193
  relatedPartPath(parts, mainPartPath, THEME_REL)
190
194
  );
191
195
  const themeFonts = themeXml === null ? NO_THEME_FONTS : readThemeFonts(parseXml(themeXml));
192
- const styles = stylesDom ? readStyles(stylesDom, themeFonts) : NO_STYLES;
193
- const defaultTableStyleId = stylesDom ? defaultTableStyleIdOf(stylesDom) : null;
194
- const defaultParagraphStyleId = stylesDom ? defaultParagraphStyleIdOf(stylesDom) : null;
195
- const paragraphDefaults = stylesDom ? readDefaultParagraphFormat(stylesDom) : {};
196
196
  const numberingXml = readPart(parts, numberingPartPath);
197
- const numbering = parseNumbering(numberingXml);
198
- const paragraphFormatting = {
199
- styles,
200
- defaultStyleId: defaultParagraphStyleId,
201
- defaults: paragraphDefaults,
202
- numbering
203
- };
197
+ const formatting = formattingContextOf(
198
+ stylesDom,
199
+ numberingXml,
200
+ themeFonts,
201
+ readCompatSettings(settingsDom)
202
+ );
204
203
  const comments = readComments(parts, mainPartPath);
205
204
  const notes = readNotes(parts, mainPartPath);
206
205
  const noteLabels = {
@@ -224,15 +223,24 @@ function importDocx(input) {
224
223
  notes,
225
224
  noteLabel
226
225
  };
226
+ const sessionId = newSessionId();
227
227
  const blockNodes = blockElements.map(
228
228
  (el, i) => withStyleFormats(
229
- buildBlock(el, i, sources, styles, defaultTableStyleId),
230
- paragraphFormatting
229
+ buildBlock(
230
+ el,
231
+ blockKey({ sessionId }, BODY_STORY_KEY, i),
232
+ sources,
233
+ formatting
234
+ ),
235
+ formatting
231
236
  )
232
237
  );
238
+ const doc = docxSchema.nodes.doc.create(null, blockNodes);
233
239
  return {
234
- doc: docxSchema.nodes.doc.create(null, blockNodes),
240
+ doc,
241
+ notes: fidelityNotesOf(doc, mainPartPath),
235
242
  session: new SessionStore({
243
+ sessionId,
236
244
  parts,
237
245
  mainPartPath,
238
246
  documentPrefix: scan.prefix,
@@ -241,19 +249,13 @@ function importDocx(input) {
241
249
  blocks: blockNodes.map((node, i) => ({ xml: scan.blocks[i].xml, node })),
242
250
  defaults: stylesDom ? readDocumentDefaults(stylesDom, themeFonts) : NO_DOCUMENT_DEFAULTS,
243
251
  defaultTabStopPt,
244
- paragraphDefaults,
245
252
  geometry,
246
- styles,
253
+ formatting,
247
254
  paragraphStyles: stylesDom ? readParagraphStyles(stylesDom) : [],
248
- defaultParagraphStyleId,
249
255
  numberingXml,
250
256
  numberingPartPath,
251
257
  comments,
252
- commentReferenceIds: new Set(
253
- commentReferencesIn(
254
- docxSchema.nodes.doc.create(null, blockNodes)
255
- ).keys()
256
- ),
258
+ commentReferenceIds: new Set(commentReferencesIn(doc).keys()),
257
259
  headersFooters
258
260
  })
259
261
  };
@@ -29,4 +29,4 @@ export interface ImportSources {
29
29
  }
30
30
  export declare const NO_IMPORT_SOURCES: ImportSources;
31
31
  /** Moves a paragraph into an editable node. null if it holds content we do not model */
32
- export declare function buildParagraph(el: Element, srcId: number | null, sources?: ImportSources): PMNode | null;
32
+ export declare function buildParagraph(el: Element, srcId: string | null, sources?: ImportSources): PMNode | null;
@@ -1,6 +1,8 @@
1
1
  // src/docx/importParagraph.ts
2
2
  import { readDrawingPicture } from "../ooxml/image.js";
3
+ import { ST_OnOff } from "../ooxml/simpleTypes.js";
3
4
  import {
5
+ attributeByLocalName,
4
6
  attrString,
5
7
  childByLocalName,
6
8
  elementChildren,
@@ -104,9 +106,8 @@ function buildRunChild(el, marks, images, comments, notes, noteLabel) {
104
106
  if (id === null) return null;
105
107
  const kind = el.localName === "footnoteReference" ? "footnote" : "endnote";
106
108
  const note = noteById(notes, kind, id);
107
- const customMarkFollows = Array.from(el.attributes).some(
108
- (entry) => entry.localName === "customMarkFollows" && !["0", "false", "off"].includes(entry.value)
109
- );
109
+ const written = attributeByLocalName(el, "customMarkFollows");
110
+ const customMarkFollows = written !== null && (ST_OnOff.parse(written) ?? true);
110
111
  return [
111
112
  docxSchema.nodes.noteReference.create(
112
113
  {
@@ -3,7 +3,7 @@
3
3
  * values for display. Unsupported grids return null so the caller can preserve their source XML.
4
4
  */
5
5
  import type { Node as PMNode } from "prosemirror-model";
6
- import { type StyleTable } from "./formatting";
6
+ import { type FormattingContext } from "./formatting";
7
7
  import { type ImportSources } from "./importParagraph";
8
8
  /** Moves a `<w:tbl>` into a table node. null if it cannot be modelled */
9
- export declare function buildTable(el: Element, srcId: number | null, sources?: ImportSources, styles?: StyleTable, defaultTableStyleId?: string | null): PMNode | null;
9
+ export declare function buildTable(el: Element, srcId: string | null, sources?: ImportSources, context?: FormattingContext): PMNode | null;