@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
@@ -1,5 +1,10 @@
1
1
  // src/ooxml/element.ts
2
- import { NAMESPACES, wName } from "./names.js";
2
+ import {
3
+ NAMESPACES,
4
+ qualify,
5
+ W_PREFIX,
6
+ wName
7
+ } from "./names.js";
3
8
  import { escapeXml, localPart } from "./xml.js";
4
9
  function attrsText(attrs) {
5
10
  return attrs.map(([name, value]) => `${name}="${escapeXml(value)}"`).join(" ");
@@ -13,19 +18,36 @@ function elementXml(name, attrs, children = []) {
13
18
  function xmlnsAttr(prefix) {
14
19
  return [`xmlns:${prefix}`, NAMESPACES[prefix]];
15
20
  }
21
+ function localNameUnder(prefix, name) {
22
+ if (!name.includes(":")) return name;
23
+ const local = localPart(name);
24
+ return name === qualify(prefix, local) ? local : null;
25
+ }
26
+ function wLocalName(name) {
27
+ return localNameUnder(W_PREFIX, name);
28
+ }
29
+ function wAttrIndex(attrs, localName) {
30
+ const qualified = attrs.findIndex(([name]) => name === wName(localName));
31
+ if (qualified !== -1) return qualified;
32
+ return attrs.findIndex(([name]) => name === localName);
33
+ }
34
+ function wAttrValue(attrs, localName) {
35
+ const at = wAttrIndex(attrs, localName);
36
+ return at === -1 ? null : attrs[at][1];
37
+ }
16
38
  function withAttr(attrs, name, value) {
17
39
  if (value === null) return withoutAttrs(attrs, [name]);
18
- const at = attrs.findIndex(([attr]) => localPart(attr) === name);
40
+ const at = wAttrIndex(attrs, name);
19
41
  if (at === -1) return [...attrs, [wName(name), value]];
20
42
  return attrs.map(
21
43
  (attr, index) => index === at ? [attr[0], value] : attr
22
44
  );
23
45
  }
24
- function withoutAttrs(attrs, localNames) {
25
- return attrs.filter(([name]) => !localNames.includes(localPart(name)));
26
- }
27
- function attrValue(attrs, localName) {
28
- return attrs.find(([name]) => localPart(name) === localName)?.[1] ?? null;
46
+ function withoutAttrs(attrs, localNames, prefix = W_PREFIX) {
47
+ return attrs.filter(([name]) => {
48
+ const local = localNameUnder(prefix, name);
49
+ return local === null || !localNames.includes(local);
50
+ });
29
51
  }
30
52
  function openTagXml(name, rawAttrs) {
31
53
  return rawAttrs ? `<${name} ${rawAttrs}>` : `<${name}>`;
@@ -33,16 +55,13 @@ function openTagXml(name, rawAttrs) {
33
55
  function emptyTagXml(name, rawAttrs) {
34
56
  return rawAttrs ? `<${name} ${rawAttrs}/>` : `<${name}/>`;
35
57
  }
36
- function attrPairs(el) {
37
- return el ? Array.from(el.attributes, (attr) => [attr.name, attr.value]) : [];
38
- }
39
58
  export {
40
- attrPairs,
41
- attrValue,
42
59
  attrsText,
43
60
  elementXml,
44
61
  emptyTagXml,
45
62
  openTagXml,
63
+ wAttrValue,
64
+ wLocalName,
46
65
  withAttr,
47
66
  withoutAttrs,
48
67
  xmlnsAttr
@@ -8,6 +8,7 @@
8
8
  /**
9
9
  * Why a document could not be opened.
10
10
  *
11
+ * - `no-xml-parser`: the runtime has no XML parser to read the package with. The one code here that is about where the call was made rather than about the file
11
12
  * - `not-a-docx`: the bytes are not a readable zip container, its entry names are not a package's, or an entry does not hold what it says it does
12
13
  * - `too-large`: the package asks to inflate to more than we open
13
14
  * - `missing-part`: the package has no main document part to read
@@ -15,7 +16,7 @@
15
16
  * - `malformed-xml`: the XML cannot be parsed, declares a DTD, or its markup is inconsistent
16
17
  * - `unsupported-content`: the document holds markup we cannot write back out unchanged
17
18
  */
18
- export type DocxImportErrorCode = "not-a-docx" | "too-large" | "missing-part" | "missing-body" | "malformed-xml" | "unsupported-content";
19
+ export type DocxImportErrorCode = "no-xml-parser" | "not-a-docx" | "too-large" | "missing-part" | "missing-body" | "malformed-xml" | "unsupported-content";
19
20
  /** Thrown when we hit a document whose content cannot be kept safely. We refuse to open it instead of losing it silently */
20
21
  export declare class DocxImportError extends Error {
21
22
  readonly code: DocxImportErrorCode;
@@ -26,7 +27,7 @@ export declare class DocxImportError extends Error {
26
27
  *
27
28
  * - `missing-numbering-part`: a new list needs a numbering.xml the document does not have
28
29
  * - `missing-content-types`: a new image needs a [Content_Types].xml the package does not have
29
- * - `unsupported-content`: the document holds a node kind we have no way to serialize
30
+ * - `unsupported-content`: the document holds a node kind we have no way to serialize, or a preserved block standing in two places, which has one original XML to write
30
31
  * - `lost-original`: a node that only carries its original XML has lost it
31
32
  * - `malformed-xml`: an original XML fragment cannot be read well enough to rewrite
32
33
  * - `invalid-table`: the table grid is inconsistent, e.g. a vertical merge outliving its rows
@@ -10,6 +10,10 @@
10
10
  *
11
11
  * An imported image holds on to its whole original `<w:drawing>` XML, so an untouched
12
12
  * image goes back out byte for byte. A resize rewrites nothing but the two extents.
13
+ *
14
+ * `emuToPx` is imported on its own by a builder working in pixels, so nothing here reads
15
+ * `NAMESPACES` at the top level: a top-level read is an impure statement a bundler keeps,
16
+ * and it would hold the naming layer in a bundle that wanted one multiplication.
13
17
  */
14
18
  /**
15
19
  * English Metric Units, the unit every DrawingML length is written in.
@@ -43,7 +43,6 @@ function toImageExtent(value) {
43
43
  const cy = positiveInt(value.cy);
44
44
  return cx === null || cy === null ? null : { cx, cy };
45
45
  }
46
- var PICTURE_URI = NAMESPACES.pic;
47
46
  function childOf(el, name) {
48
47
  return el ? childByLocalName(el, name) : null;
49
48
  }
@@ -63,7 +62,7 @@ function readDrawingPicture(drawing) {
63
62
  const extent = readExtent(childOf(inline, "extent"));
64
63
  if (!extent) return null;
65
64
  const graphicData = childOf(childOf(inline, "graphic"), "graphicData");
66
- if (!graphicData || graphicData.getAttribute("uri") !== PICTURE_URI) {
65
+ if (!graphicData || graphicData.getAttribute("uri") !== NAMESPACES.pic) {
67
66
  return null;
68
67
  }
69
68
  const blip = childOf(
@@ -83,13 +82,11 @@ function withExtent(xml, extent) {
83
82
  const size = `cx="${extent.cx}" cy="${extent.cy}"`;
84
83
  return xml.replace(EXTENT_TAG, (_match, prefix) => `<${prefix ?? ""}extent ${size}/>`).replace(EXT_TAG, (_match, prefix) => `<${prefix ?? ""}ext ${size}/>`);
85
84
  }
86
- var WP_NS = NAMESPACES.wp;
87
- var A_NS = NAMESPACES.a;
88
85
  function imageDrawingXml(image) {
89
86
  const size = `cx="${image.extent.cx}" cy="${image.extent.cy}"`;
90
87
  const name = `Picture ${image.docPrId}`;
91
88
  const descr = image.alt === null ? "" : ` descr="${escapeXml(image.alt)}"`;
92
- return `<w:drawing><wp:inline xmlns:wp="${WP_NS}" distT="0" distB="0" distL="0" distR="0"><wp:extent ${size}/><wp:effectExtent l="0" t="0" r="0" b="0"/><wp:docPr id="${image.docPrId}" name="${name}"${descr}/><wp:cNvGraphicFramePr><a:graphicFrameLocks xmlns:a="${A_NS}" noChangeAspect="1"/></wp:cNvGraphicFramePr><a:graphic xmlns:a="${A_NS}"><a:graphicData uri="${PICTURE_URI}"><pic:pic xmlns:pic="${PICTURE_URI}"><pic:nvPicPr><pic:cNvPr id="${image.docPrId}" name="${name}"${descr}/><pic:cNvPicPr/></pic:nvPicPr><pic:blipFill><a:blip xmlns:r="${R_NS}" r:embed="${escapeXml(image.relId)}"/><a:stretch><a:fillRect/></a:stretch></pic:blipFill><pic:spPr><a:xfrm><a:off x="0" y="0"/><a:ext ${size}/></a:xfrm><a:prstGeom prst="rect"><a:avLst/></a:prstGeom></pic:spPr></pic:pic></a:graphicData></a:graphic></wp:inline></w:drawing>`;
89
+ return `<w:drawing><wp:inline xmlns:wp="${NAMESPACES.wp}" distT="0" distB="0" distL="0" distR="0"><wp:extent ${size}/><wp:effectExtent l="0" t="0" r="0" b="0"/><wp:docPr id="${image.docPrId}" name="${name}"${descr}/><wp:cNvGraphicFramePr><a:graphicFrameLocks xmlns:a="${NAMESPACES.a}" noChangeAspect="1"/></wp:cNvGraphicFramePr><a:graphic xmlns:a="${NAMESPACES.a}"><a:graphicData uri="${NAMESPACES.pic}"><pic:pic xmlns:pic="${NAMESPACES.pic}"><pic:nvPicPr><pic:cNvPr id="${image.docPrId}" name="${name}"${descr}/><pic:cNvPicPr/></pic:nvPicPr><pic:blipFill><a:blip xmlns:r="${R_NS}" r:embed="${escapeXml(image.relId)}"/><a:stretch><a:fillRect/></a:stretch></pic:blipFill><pic:spPr><a:xfrm><a:off x="0" y="0"/><a:ext ${size}/></a:xfrm><a:prstGeom prst="rect"><a:avLst/></a:prstGeom></pic:spPr></pic:pic></a:graphicData></a:graphic></wp:inline></w:drawing>`;
93
90
  }
94
91
  export {
95
92
  EMU_PER_PX,
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Putting children into the root element of a package part, and leaving every byte around them as
3
+ * the part arrived with.
4
+ *
5
+ * A part is text the writers cut and join, never a tree they build again, so the spots to cut at
6
+ * are found in the text: the root's opening tag past the prolog, and its closing tag counted to
7
+ * by depth. Searching for the closing tag instead would take a `</Relationships>` inside a
8
+ * comment for the end of the part, and miss the root altogether when it is written under a
9
+ * prefix or closes on itself.
10
+ */
11
+ import type { KnownPrefix } from "./names";
12
+ /** One child to put into a part, named by the local part of its tag so the child order can place it */
13
+ export interface PartChild {
14
+ name: string;
15
+ xml: string;
16
+ }
17
+ /**
18
+ * What goes into a part's root. Every field is optional and they compose: the children are
19
+ * replaced first, then the inserts are placed among them, then the prepend and the append go on
20
+ * either end, and last the opening tag is rewritten.
21
+ */
22
+ export interface PartSplice {
23
+ /** The root's local name. Any prefix, a self-closing root, a prolog and comments are all handled */
24
+ root: string;
25
+ /** Children put at the spot `CHILD_ORDER[root]` lays down, ahead of the first child that order puts after them */
26
+ insert?: readonly PartChild[];
27
+ /** Put in as the last children */
28
+ append?: string;
29
+ /** Put in right after the opening tag */
30
+ prepend?: string;
31
+ /** Written in place of every child */
32
+ replaceChildren?: string;
33
+ /**
34
+ * Rewrites the opening tag, handed as it is about to be written: opened when children go into a
35
+ * root that closed on itself, else as it arrived.
36
+ */
37
+ rootTag?: (open: string) => string;
38
+ }
39
+ /**
40
+ * Why the part cannot be spliced around its root, or null when it can. The same refusal
41
+ * `splicePart` throws, so a caller may ask ahead and be answered in the same words.
42
+ */
43
+ export declare function partRootProblem(xml: string, root: string): string | null;
44
+ /**
45
+ * The part with the children of `splice` put into its root, and every byte outside what was put
46
+ * in standing as it arrived. A root that closed on itself is opened to hold them.
47
+ *
48
+ * Throws `malformed-xml` naming the part when the root is not there or never closes.
49
+ */
50
+ export declare function splicePart(xml: string, splice: PartSplice): string;
51
+ /**
52
+ * The prefix the root's name carries, colon included, so a child written beside the ones the part
53
+ * holds is spelled the way the root is. Empty for a root written under the default namespace, and
54
+ * for a part with no root, which the splice that follows refuses.
55
+ */
56
+ export declare function rootPrefixOf(xml: string): string;
57
+ export interface RootDeclarations {
58
+ /** The prefixes the root has to bind, and what to bind each to where it does not yet */
59
+ namespaces: Partial<Record<KnownPrefix, string>>;
60
+ /** The tokens `mc:Ignorable` has to name, added to the ones it names already */
61
+ ignorable?: readonly string[];
62
+ }
63
+ /**
64
+ * The part with its root binding every prefix named and its `mc:Ignorable` naming every token
65
+ * asked for, each declared once: one the root declares already is left as it was written.
66
+ */
67
+ export declare function ensureRootDeclarations(xml: string, declarations: RootDeclarations): string;
@@ -0,0 +1,169 @@
1
+ // src/ooxml/partSplice.ts
2
+ import { childOrderOf } from "./childOrder.js";
3
+ import { attrsText } from "./element.js";
4
+ import { DocxExportError } from "./errors.js";
5
+ import { parseAttrs, readTag, rootTagAt } from "./tagScan.js";
6
+ import { localPart } from "./xml.js";
7
+ function closingTagAt(xml, open) {
8
+ let depth = 1;
9
+ let at = open.end;
10
+ for (; ; ) {
11
+ const lt = xml.indexOf("<", at);
12
+ if (lt === -1) return null;
13
+ const tag = readTag(xml, lt);
14
+ if (tag === null) return null;
15
+ if (tag.kind === "open") depth += 1;
16
+ if (tag.kind === "close") {
17
+ depth -= 1;
18
+ if (depth === 0) return tag.name === open.name ? lt : null;
19
+ }
20
+ at = tag.end;
21
+ }
22
+ }
23
+ function readRoot(xml, root) {
24
+ const openAt = rootTagAt(xml);
25
+ const open = openAt === -1 ? null : readTag(xml, openAt);
26
+ if (open === null || open.kind === "close" || localPart(open.name) !== root) {
27
+ return { problem: `the ${root} part has no ${root} root element` };
28
+ }
29
+ if (open.kind === "empty") return { root: { openAt, open, closeAt: null } };
30
+ const closeAt = closingTagAt(xml, open);
31
+ if (closeAt === null) {
32
+ return { problem: `the ${root} part has no closing ${root} tag` };
33
+ }
34
+ return { root: { openAt, open, closeAt } };
35
+ }
36
+ function partRootProblem(xml, root) {
37
+ const reading = readRoot(xml, root);
38
+ return "problem" in reading ? reading.problem : null;
39
+ }
40
+ function directChildren(inner) {
41
+ const children = [];
42
+ let depth = 0;
43
+ let at = 0;
44
+ for (; ; ) {
45
+ const lt = inner.indexOf("<", at);
46
+ if (lt === -1) return children;
47
+ const tag = readTag(inner, lt);
48
+ if (tag === null) return children;
49
+ if (depth === 0 && (tag.kind === "open" || tag.kind === "empty")) {
50
+ children.push({ name: localPart(tag.name), at: lt });
51
+ }
52
+ if (tag.kind === "open") depth += 1;
53
+ if (tag.kind === "close") depth -= 1;
54
+ at = tag.end;
55
+ }
56
+ }
57
+ function insertionAt(children, name, order, root, end) {
58
+ const target = order.indexOf(name);
59
+ if (target === -1) {
60
+ throw new Error(
61
+ `${name} is not a child the order of ${root} knows; add it to CHILD_ORDER`
62
+ );
63
+ }
64
+ const after = children.find((child) => order.indexOf(child.name) > target);
65
+ return after === void 0 ? end : after.at;
66
+ }
67
+ function withInserted(inner, inserts, root) {
68
+ if (inserts.length === 0) return inner;
69
+ const order = childOrderOf(root);
70
+ const children = directChildren(inner);
71
+ const placed = inserts.map((child) => ({
72
+ at: insertionAt(children, child.name, order, root, inner.length),
73
+ xml: child.xml,
74
+ rank: order.indexOf(child.name)
75
+ })).sort((a, b) => a.at - b.at || a.rank - b.rank);
76
+ let out = "";
77
+ let from = 0;
78
+ for (const { at, xml } of placed) {
79
+ out += inner.slice(from, at) + xml;
80
+ from = at;
81
+ }
82
+ return out + inner.slice(from);
83
+ }
84
+ function splicePart(xml, splice) {
85
+ const reading = readRoot(xml, splice.root);
86
+ if ("problem" in reading) {
87
+ throw new DocxExportError("malformed-xml", reading.problem);
88
+ }
89
+ const { openAt, open, closeAt } = reading.root;
90
+ const inner = closeAt === null ? "" : xml.slice(open.end, closeAt);
91
+ const children = (splice.prepend ?? "") + withInserted(
92
+ splice.replaceChildren ?? inner,
93
+ splice.insert ?? [],
94
+ splice.root
95
+ ) + (splice.append ?? "");
96
+ const openTag = xml.slice(openAt, open.end);
97
+ const opened = closeAt === null && children !== "" ? `${openTag.slice(0, -2)}>` : openTag;
98
+ const head = xml.slice(0, openAt) + (splice.rootTag?.(opened) ?? opened);
99
+ if (closeAt !== null) return head + children + xml.slice(closeAt);
100
+ if (children === "") return head + xml.slice(open.end);
101
+ return `${head}${children}</${open.name}>${xml.slice(open.end)}`;
102
+ }
103
+ function rootNameOf(xml) {
104
+ const at = rootTagAt(xml);
105
+ const open = at === -1 ? null : readTag(xml, at);
106
+ return open === null || open.kind === "close" ? null : open.name;
107
+ }
108
+ function rootPrefixOf(xml) {
109
+ const name = rootNameOf(xml) ?? "";
110
+ const colon = name.indexOf(":");
111
+ return colon === -1 ? "" : name.slice(0, colon + 1);
112
+ }
113
+ var IGNORABLE = /\smc:Ignorable\s*=\s*(["'])([^"']*)\1/;
114
+ function withDeclarations(openTag, { namespaces, ignorable }) {
115
+ const tag = readTag(openTag, 0);
116
+ const closeLength = tag?.kind === "empty" ? 2 : 1;
117
+ const attrs = tag === null ? null : parseAttrs(openTag.slice(tag.nameEnd, tag.end - closeLength));
118
+ if (tag === null || attrs === null) {
119
+ throw new DocxExportError(
120
+ "malformed-xml",
121
+ "the root element's attributes could not be read"
122
+ );
123
+ }
124
+ const declared = new Set(attrs.map(([name]) => name));
125
+ const additions = [];
126
+ for (const [prefix, namespace] of Object.entries(namespaces)) {
127
+ if (namespace !== void 0 && !declared.has(`xmlns:${prefix}`)) {
128
+ additions.push([`xmlns:${prefix}`, namespace]);
129
+ }
130
+ }
131
+ const ignoring = IGNORABLE.exec(openTag);
132
+ let tokens = null;
133
+ if (ignorable !== void 0 && ignorable.length > 0) {
134
+ if (ignoring === null) {
135
+ additions.push(["mc:Ignorable", ignorable.join(" ")]);
136
+ } else {
137
+ const had = (attrs.find(([name]) => name === "mc:Ignorable")?.[1] ?? "").split(/\s+/).filter(Boolean);
138
+ const missing = ignorable.filter((token) => !had.includes(token));
139
+ if (missing.length > 0) tokens = [...had, ...missing];
140
+ }
141
+ }
142
+ let next = openTag;
143
+ if (ignoring !== null && tokens !== null) {
144
+ const rewritten = ` mc:Ignorable=${ignoring[1]}${tokens.join(" ")}${ignoring[1]}`;
145
+ next = next.slice(0, ignoring.index) + rewritten + next.slice(ignoring.index + ignoring[0].length);
146
+ }
147
+ if (additions.length === 0) return next;
148
+ const end = next.length - closeLength;
149
+ return `${next.slice(0, end)} ${attrsText(additions)}${next.slice(end)}`;
150
+ }
151
+ function ensureRootDeclarations(xml, declarations) {
152
+ const root = rootNameOf(xml);
153
+ if (root === null) {
154
+ throw new DocxExportError(
155
+ "malformed-xml",
156
+ "the part has no root element to declare a namespace on"
157
+ );
158
+ }
159
+ return splicePart(xml, {
160
+ root: localPart(root),
161
+ rootTag: (open) => withDeclarations(open, declarations)
162
+ });
163
+ }
164
+ export {
165
+ ensureRootDeclarations,
166
+ partRootProblem,
167
+ rootPrefixOf,
168
+ splicePart
169
+ };
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Handles the XML fragments that carry formatting, such as `<w:rPr>`, `<w:pPr>`, and `<w:tcPr>`.
3
+ *
4
+ * A fragment is never rebuilt: only the one child a job names is sliced out and swapped, so the
5
+ * rest (borders, shading, margins) stays as the original text wrote it. OOXML lays down the order
6
+ * of the children, so the spot to insert a child that was not there is found by that same order.
7
+ */
8
+ import { type XmlAttr } from "./element";
9
+ export interface PropsChild {
10
+ /** The name with its namespace prefix stripped off (e.g. `gridSpan`) */
11
+ name: string;
12
+ /** This child's original XML fragment exactly as it was */
13
+ xml: string;
14
+ /**
15
+ * Whatever stood between the child before this one and this one: line breaks a producer laid
16
+ * out, comments, anything that is not an element. Carried so that rewriting one child does not
17
+ * quietly drop the rest of what the fragment said. Absent rather than empty when nothing did.
18
+ */
19
+ before?: string;
20
+ }
21
+ export interface Props {
22
+ /** The opening tag name exactly as written (e.g. `w:tcPr`) */
23
+ tag: string;
24
+ attrs: string | null;
25
+ children: PropsChild[];
26
+ /** The same, for what stood between the last child and the closing tag */
27
+ tail?: string;
28
+ }
29
+ /**
30
+ * What stood inside a single element, and "" for one that stood empty or cannot be made out.
31
+ *
32
+ * The opening tag is read rather than scanned for, so a `>` inside an attribute value does not
33
+ * pass for the end of it.
34
+ */
35
+ export declare function innerXml(xml: string): string;
36
+ /**
37
+ * Splits a formatting fragment into its opening tag and its list of children.
38
+ * null if its shape cannot be made out (in which case leaving the original untouched is the safe move).
39
+ */
40
+ export declare function parseProps(xml: string): Props | null;
41
+ /**
42
+ * With nothing to say, the formatting fragment itself is not written. A fragment holding only
43
+ * whitespace says nothing; one holding a comment does, so it is written back.
44
+ */
45
+ export declare function renderProps(props: Props): string;
46
+ /**
47
+ * Replaces a single child with new XML.
48
+ *
49
+ * A null `xml` removes that child. A child that was not there goes into the spot `CHILD_ORDER`
50
+ * lays down for this fragment's own tag, so the caller names what it is writing and not where it
51
+ * goes. Naming the element this fragment stands inside picks the right order where one element
52
+ * carries children in a different order under a different parent (`pPr/rPr`).
53
+ */
54
+ export declare function setChild(props: Props, name: string, xml: string | null, parent?: string): Props;
55
+ export declare function propsChild(children: readonly PropsChild[], name: string): PropsChild | undefined;
56
+ /**
57
+ * The opening tag's attributes as pairs, and null when their shape cannot be made out.
58
+ *
59
+ * The pairs are worked out on demand rather than kept on `Props`, because an untouched fragment
60
+ * has to go back out with the spacing, the quoting and the escaping its producer chose. Only a
61
+ * fragment something writes to passes through `withAttrs`, which is where they are written again.
62
+ */
63
+ export declare function attrsOf(props: Props): XmlAttr[] | null;
64
+ /** The fragment with these attributes written in place of the ones its opening tag carried */
65
+ export declare function withAttrs(props: Props, attrs: readonly XmlAttr[]): Props;
66
+ /**
67
+ * One child of a fragment as it was written: the tag, prefix included, so an edited child keeps
68
+ * the spelling the document chose, and its attributes. A child that is not there has neither, so
69
+ * no tag with attributes hanging off it can be made.
70
+ */
71
+ export type ChildElement = {
72
+ tag: string;
73
+ attrs: readonly XmlAttr[];
74
+ } | {
75
+ tag: null;
76
+ attrs: readonly [];
77
+ };
78
+ /**
79
+ * The tag and attributes of one child. A child that is not there reads as no tag and no
80
+ * attributes, and null says its shape cannot be made out, which leaves the caller to back out
81
+ * rather than write over markup it could not read.
82
+ */
83
+ export declare function childElement(props: Props, name: string): ChildElement | null;
84
+ /**
85
+ * Always writes the element, closing it on its own when it holds nothing. Whitespace alone counts
86
+ * as nothing, the same as `renderProps` reads it, so a pretty-printed fragment emptied of its
87
+ * children collapses rather than keeping the line breaks that stood between them.
88
+ */
89
+ export declare function renderElement(props: Props): string;
90
+ /**
91
+ * An element whose children go in the order `CHILD_ORDER` lays down, whatever order they are
92
+ * handed in. This is how a fragment written from scratch follows the same order an edited one
93
+ * is held to, and a child the order does not know is refused the same way.
94
+ */
95
+ export declare function orderedElement(tag: string, attrs: readonly XmlAttr[], children: readonly PropsChild[]): string;
96
+ /**
97
+ * Edits one child down a path of names, parsing only the fragments the path runs through and
98
+ * leaving every other child as the original text wrote it.
99
+ *
100
+ * The callback is handed the child the path names, null for one that is not there, and answers
101
+ * with what it is to become, null to take it away. A container along the path that was not there
102
+ * is written, and one the edit leaves empty is taken away with it. null when a fragment on the
103
+ * path cannot be made out, which leaves the caller to back out.
104
+ */
105
+ export declare function editChild(props: Props, path: readonly [string, ...string[]], edit: (child: Props | null) => Props | null): Props | null;
106
+ /**
107
+ * Reads a single formatting fragment into an element.
108
+ *
109
+ * A fragment carries no namespace declarations, so they are put back on as it is wrapped.
110
+ * null if its shape cannot be made out, in which case the caller leaves the display values alone.
111
+ */
112
+ export declare function parsePropsXml(xml: string): Element | null;