@stll/folio-core 0.10.0 → 0.12.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 (217) hide show
  1. package/dist/ai-edits/apply.js +602 -50
  2. package/dist/ai-edits/block-identity.d.ts +4 -0
  3. package/dist/ai-edits/block-identity.js +9 -0
  4. package/dist/ai-edits/blockRange.d.ts +28 -1
  5. package/dist/ai-edits/blockRange.js +67 -1
  6. package/dist/ai-edits/headless.d.ts +4 -4
  7. package/dist/ai-edits/headless.js +9 -9
  8. package/dist/ai-edits/index.d.ts +2 -2
  9. package/dist/ai-edits/index.js +2 -2
  10. package/dist/ai-edits/read.d.ts +7 -7
  11. package/dist/ai-edits/read.js +110 -3
  12. package/dist/ai-edits/table-cell-mutations.d.ts +48 -0
  13. package/dist/ai-edits/table-cell-mutations.js +237 -0
  14. package/dist/ai-edits/table-mutation-plan.d.ts +38 -0
  15. package/dist/ai-edits/table-mutation-plan.js +90 -0
  16. package/dist/ai-edits/table-row-column-mutations.d.ts +100 -0
  17. package/dist/ai-edits/table-row-column-mutations.js +300 -0
  18. package/dist/ai-edits/table-targets.d.ts +32 -0
  19. package/dist/ai-edits/table-targets.js +70 -0
  20. package/dist/ai-edits/types.d.ts +34 -0
  21. package/dist/compat/eigenpal.js +1 -1
  22. package/dist/content-controls/mutateContentControls.js +1 -1
  23. package/dist/controller/activeEditorStory.d.ts +36 -0
  24. package/dist/controller/activeEditorStory.js +30 -0
  25. package/dist/controller/collaborationModules.d.ts +7 -0
  26. package/dist/controller/collaborationModules.js +15 -0
  27. package/dist/controller/contentControlWidgetController.d.ts +55 -0
  28. package/dist/controller/contentControlWidgetController.js +141 -0
  29. package/dist/controller/folioEditor.d.ts +1 -1
  30. package/dist/controller/fontReadiness.js +2 -0
  31. package/dist/controller/headerFooterEditorManager.d.ts +41 -0
  32. package/dist/controller/headerFooterEditorManager.js +182 -0
  33. package/dist/controller/hiddenEditorApi.js +2 -2
  34. package/dist/controller/hiddenEditorManager.d.ts +13 -2
  35. package/dist/controller/hiddenEditorManager.js +32 -11
  36. package/dist/controller/layoutPipeline.d.ts +2 -0
  37. package/dist/controller/layoutPipeline.js +16 -1
  38. package/dist/controller/noteEditorManager.d.ts +36 -0
  39. package/dist/controller/noteEditorManager.js +210 -0
  40. package/dist/document-operations.d.ts +35 -3
  41. package/dist/document-operations.js +161 -3
  42. package/dist/docx/blockContentParser.js +2 -2
  43. package/dist/docx/commentParser.js +6 -1
  44. package/dist/docx/conformance.js +8 -1
  45. package/dist/docx/documentGrid.d.ts +8 -0
  46. package/dist/docx/documentGrid.js +8 -0
  47. package/dist/docx/drawingUtils.js +1 -1
  48. package/dist/docx/ensureParaIds.js +1 -1
  49. package/dist/docx/fieldParser.js +1 -1
  50. package/dist/docx/notePropertiesParser.js +1 -1
  51. package/dist/docx/numberingParser.js +1 -1
  52. package/dist/docx/paragraphParser.js +63 -58
  53. package/dist/docx/paragraphTextBoxEnrichment.d.ts +2 -1
  54. package/dist/docx/paragraphTextBoxEnrichment.js +67 -13
  55. package/dist/docx/paragraphTraversal.js +1 -1
  56. package/dist/docx/parser.js +3 -3
  57. package/dist/docx/rezip.js +1 -1
  58. package/dist/docx/runParser.js +4 -2
  59. package/dist/docx/sdtProperties.js +1 -1
  60. package/dist/docx/sectionParser.js +1 -1
  61. package/dist/docx/selectiveSave.js +1 -1
  62. package/dist/docx/serializer/commentSerializer.js +1 -2
  63. package/dist/docx/serializer/documentSerializer.js +2 -2
  64. package/dist/docx/serializer/headerFooterSerializer.js +2 -2
  65. package/dist/docx/serializer/noteSerializer.js +2 -2
  66. package/dist/docx/serializer/paragraphSerializer.js +7 -1
  67. package/dist/docx/serializer/runSerializer.js +4 -2
  68. package/dist/docx/serializer/stylesSerializer.js +1 -1
  69. package/dist/docx/serializer/tableSerializer.d.ts +5 -4
  70. package/dist/docx/serializer/tableSerializer.js +17 -13
  71. package/dist/docx/server/applyDocxXmlPatchProposal.d.ts +87 -0
  72. package/dist/docx/server/applyDocxXmlPatchProposal.js +204 -0
  73. package/dist/docx/server/boundedArchive.d.ts +14 -3
  74. package/dist/docx/server/boundedArchive.js +56 -11
  75. package/dist/docx/server/evaluateDocxXmlPatchProposal.d.ts +85 -0
  76. package/dist/docx/server/evaluateDocxXmlPatchProposal.js +361 -0
  77. package/dist/docx/server/inspectDocxPackage.d.ts +70 -0
  78. package/dist/docx/server/inspectDocxPackage.js +217 -0
  79. package/dist/docx/server/validateDocxConformance.d.ts +41 -0
  80. package/dist/docx/server/validateDocxConformance.js +336 -0
  81. package/dist/docx/settingsParser.js +11 -1
  82. package/dist/docx/shapeParser.js +1 -1
  83. package/dist/docx/styleParser.js +4 -2
  84. package/dist/docx/tableParser.js +18 -7
  85. package/dist/docx/textBoxParser.d.ts +3 -3
  86. package/dist/docx/textBoxParser.js +16 -12
  87. package/dist/docx/xmlSafety.d.ts +5 -0
  88. package/dist/docx/xmlSafety.js +9 -0
  89. package/dist/fields/resolveFieldValues.js +1 -3
  90. package/dist/i18n/messages/catalogs.gen.d.ts +17 -0
  91. package/dist/i18n/messages/catalogs.gen.js +17 -0
  92. package/dist/i18n/messages/messages.gen.d.ts +1 -0
  93. package/dist/index.js +1 -1
  94. package/dist/layout-bridge/convert/footnoteLayout.d.ts +1 -0
  95. package/dist/layout-bridge/convert/footnoteLayout.js +47 -40
  96. package/dist/layout-bridge/convert/headerFooterLayout.d.ts +1 -0
  97. package/dist/layout-bridge/convert/headerFooterLayout.js +3 -0
  98. package/dist/layout-bridge/convert/templatePreviewFlow.js +3 -3
  99. package/dist/layout-bridge/convert/toFlowBlocks.d.ts +4 -2
  100. package/dist/layout-bridge/convert/toFlowBlocks.js +136 -20
  101. package/dist/layout-bridge/dom/noteStoryDom.d.ts +6 -0
  102. package/dist/layout-bridge/dom/noteStoryDom.js +17 -0
  103. package/dist/layout-bridge/engine/clickToPosition.js +2 -2
  104. package/dist/layout-engine/index.d.ts +2 -2
  105. package/dist/layout-engine/index.js +43 -28
  106. package/dist/layout-engine/justificationCompatibility.d.ts +6 -0
  107. package/dist/layout-engine/justificationCompatibility.js +8 -0
  108. package/dist/layout-engine/keep-together.js +11 -1
  109. package/dist/layout-engine/measure/cache.js +21 -5
  110. package/dist/layout-engine/measure/lineBreakProvider.js +53 -34
  111. package/dist/layout-engine/measure/measureBlocks.js +6 -3
  112. package/dist/layout-engine/measure/measureContainer.js +1 -1
  113. package/dist/layout-engine/measure/measureHelpers.d.ts +1 -1
  114. package/dist/layout-engine/measure/measureHelpers.js +3 -3
  115. package/dist/layout-engine/measure/measureParagraph.js +69 -25
  116. package/dist/layout-engine/measure/measureProvider.js +4 -3
  117. package/dist/layout-engine/measure/tableCellFlow.js +5 -2
  118. package/dist/layout-engine/measure/textBoxParagraphLayout.d.ts +9 -7
  119. package/dist/layout-engine/measure/textBoxParagraphLayout.js +22 -6
  120. package/dist/layout-engine/measure/textMeasurementPolicy.d.ts +3 -1
  121. package/dist/layout-engine/measure/textMeasurementPolicy.js +7 -1
  122. package/dist/layout-engine/paginator.js +5 -1
  123. package/dist/layout-engine/paragraphSpacing.d.ts +28 -4
  124. package/dist/layout-engine/paragraphSpacing.js +107 -13
  125. package/dist/layout-engine/renderedBreakReconciliation.js +1 -1
  126. package/dist/layout-engine/types.d.ts +26 -13
  127. package/dist/layout-painter/registry/modules/textBox.js +5 -1
  128. package/dist/layout-painter/renderPage.d.ts +5 -2
  129. package/dist/layout-painter/renderPage.js +56 -12
  130. package/dist/layout-painter/renderParagraph.js +22 -6
  131. package/dist/layout-painter/renderTable.d.ts +5 -1
  132. package/dist/layout-painter/renderTable.js +5 -2
  133. package/dist/layout-painter/renderTextBox.d.ts +2 -1
  134. package/dist/layout-painter/renderTextBox.js +25 -14
  135. package/dist/managers/DocumentLoaderManager.js +1 -1
  136. package/dist/managers/FindReplaceManager.d.ts +8 -0
  137. package/dist/managers/FindReplaceManager.js +18 -0
  138. package/dist/managers/TableSelectionManager.d.ts +6 -0
  139. package/dist/managers/TableSelectionManager.js +14 -0
  140. package/dist/model.js +1 -1
  141. package/dist/prosemirror/attrs/index.d.ts +4 -2
  142. package/dist/prosemirror/attrs/index.js +154 -18
  143. package/dist/prosemirror/commands/comments.d.ts +3 -3
  144. package/dist/prosemirror/commands/comments.js +248 -12
  145. package/dist/prosemirror/commands/contentControls.js +1 -1
  146. package/dist/prosemirror/commands/index.js +1 -1
  147. package/dist/prosemirror/commands/pageBreak.js +1 -1
  148. package/dist/prosemirror/commands/propertyChangeScope.d.ts +2 -2
  149. package/dist/prosemirror/commands/propertyChangeScope.js +3 -2
  150. package/dist/prosemirror/commands/tableCellMergeResolution.d.ts +9 -0
  151. package/dist/prosemirror/commands/tableCellMergeResolution.js +181 -0
  152. package/dist/prosemirror/conversion/fromProseDoc.d.ts +2 -1
  153. package/dist/prosemirror/conversion/fromProseDoc.js +286 -61
  154. package/dist/prosemirror/conversion/sdtAttrs.d.ts +9 -0
  155. package/dist/prosemirror/conversion/sdtAttrs.js +49 -0
  156. package/dist/prosemirror/conversion/toProseDoc.d.ts +2 -1
  157. package/dist/prosemirror/conversion/toProseDoc.js +347 -98
  158. package/dist/prosemirror/extensions/ExtensionManager.js +1 -1
  159. package/dist/prosemirror/extensions/StarterKit.js +6 -1
  160. package/dist/prosemirror/extensions/core/ParagraphExtension.js +4 -1
  161. package/dist/prosemirror/extensions/features/BaseKeymapExtension.js +1 -0
  162. package/dist/prosemirror/extensions/features/ListExtension.js +1 -1
  163. package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.d.ts +10 -1
  164. package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.js +93 -2
  165. package/dist/prosemirror/extensions/features/ParagraphChangeTrackerExtension.d.ts +2 -1
  166. package/dist/prosemirror/extensions/features/ParagraphChangeTrackerExtension.js +6 -2
  167. package/dist/prosemirror/extensions/marks/CharacterSpacingExtension.js +1 -1
  168. package/dist/prosemirror/extensions/marks/TrackedChangeExtensions.d.ts +9 -1
  169. package/dist/prosemirror/extensions/marks/TrackedChangeExtensions.js +24 -1
  170. package/dist/prosemirror/extensions/nodes/FieldExtension.js +1 -1
  171. package/dist/prosemirror/extensions/nodes/RenderedPageBreakExtension.d.ts +7 -0
  172. package/dist/prosemirror/extensions/nodes/RenderedPageBreakExtension.js +22 -0
  173. package/dist/prosemirror/extensions/nodes/TableExtension.js +9 -3
  174. package/dist/prosemirror/extensions/nodes/TextBoxAnchorExtension.d.ts +13 -0
  175. package/dist/prosemirror/extensions/nodes/TextBoxAnchorExtension.js +41 -0
  176. package/dist/prosemirror/extensions/nodes/TextBoxExtension.d.ts +5 -2
  177. package/dist/prosemirror/extensions/nodes/TextBoxExtension.js +4 -1
  178. package/dist/prosemirror/findReplaceSelection.d.ts +6 -1
  179. package/dist/prosemirror/findReplaceSelection.js +57 -13
  180. package/dist/prosemirror/plugins/contentControlWidgets.d.ts +34 -8
  181. package/dist/prosemirror/plugins/contentControlWidgets.js +125 -91
  182. package/dist/prosemirror/plugins/selectionTracker.js +1 -0
  183. package/dist/prosemirror/plugins/suggestionMode.js +1 -1
  184. package/dist/prosemirror/schema/index.d.ts +2 -2
  185. package/dist/prosemirror/schema/marks.d.ts +5 -1
  186. package/dist/prosemirror/schema/nodes.d.ts +64 -7
  187. package/dist/prosemirror/selectionState.js +1 -0
  188. package/dist/prosemirror/styles/resolvedStyleAttrs.js +1 -0
  189. package/dist/prosemirror/tableCellMergeRevision.d.ts +9 -0
  190. package/dist/prosemirror/tableCellMergeRevision.js +22 -0
  191. package/dist/prosemirror/textBoxAnchorAttrs.d.ts +9 -0
  192. package/dist/prosemirror/textBoxAnchorAttrs.js +33 -0
  193. package/dist/prosemirror/utils/extractTrackedChanges.d.ts +1 -1
  194. package/dist/prosemirror/utils/extractTrackedChanges.js +62 -14
  195. package/dist/prosemirror/validation.js +9 -1
  196. package/dist/render-dom/BodySelectionOverlay.d.ts +31 -0
  197. package/dist/render-dom/BodySelectionOverlay.js +76 -0
  198. package/dist/render-dom/HeaderFooterSelectionOverlay.d.ts +28 -0
  199. package/dist/render-dom/HeaderFooterSelectionOverlay.js +121 -0
  200. package/dist/render-dom/RemoteSelectionOverlay.d.ts +30 -0
  201. package/dist/render-dom/RemoteSelectionOverlay.js +67 -0
  202. package/dist/render-dom/RenderedDomContext.d.ts +40 -0
  203. package/dist/render-dom/RenderedDomContext.js +126 -0
  204. package/dist/render-dom/resolveSidebarItemPositions.d.ts +33 -0
  205. package/dist/render-dom/resolveSidebarItemPositions.js +45 -0
  206. package/dist/server.d.ts +6 -2
  207. package/dist/server.js +5 -1
  208. package/dist/utils/findReplace.d.ts +32 -0
  209. package/dist/utils/findReplace.js +118 -0
  210. package/dist/utils/fontFamilyMerge.js +9 -11
  211. package/dist/utils/fontLoader.js +3 -2
  212. package/dist/utils/fontResolver.js +22 -4
  213. package/dist/utils/fontWeights.d.ts +1 -1
  214. package/dist/utils/fontWeights.js +1 -1
  215. package/dist/utils/headerFooter.js +24 -1
  216. package/dist/utils/paragraphFormattingMerge.js +1 -0
  217. package/package.json +1 -1
@@ -0,0 +1,41 @@
1
+ import { document_d_exports } from "../../types/document.js";
2
+ import { DocxArchiveOptions } from "./boundedArchive.js";
3
+
4
+ //#region src/docx/server/validateDocxConformance.d.ts
5
+ declare const FOLIO_DOCX_CONFORMANCE_REPORT_VERSION: 1;
6
+ declare const FOLIO_DOCX_CONFORMANCE_PROFILE: "folio-supported-v1";
7
+ declare const FOLIO_DOCX_CONFORMANCE_CHECKS: readonly ["archive-safety", "required-parts", "xml-well-formedness", "package-roots", "conformance-class", "canonical-model"];
8
+ declare const FOLIO_DOCX_CONFORMANCE_ISSUE_CODES: readonly ["archive-load-failed", "archive-invalid-options", "archive-input-too-large", "archive-too-many-entries", "archive-entry-too-large", "archive-total-too-large", "required-part-missing", "xml-doctype-forbidden", "xml-not-well-formed", "xml-read-failed", "required-xml-unreadable", "package-root-invalid", "conformance-class-unknown", "model-invalid", "model-warning", "parser-recovery", "parser-unsupported", "parser-failed", "encrypted-container", "container-not-zip"];
9
+ type FolioDocxConformanceCheckId = (typeof FOLIO_DOCX_CONFORMANCE_CHECKS)[number];
10
+ type FolioDocxConformanceIssueCode = (typeof FOLIO_DOCX_CONFORMANCE_ISSUE_CODES)[number];
11
+ type FolioDocxConformanceCheckStatus = "passed" | "failed" | "indeterminate" | "not-run";
12
+ type FolioDocxConformanceStatus = "invalid" | "conformant" | "indeterminate";
13
+ type FolioDocxConformanceIssue = {
14
+ readonly check: FolioDocxConformanceCheckId;
15
+ readonly code: FolioDocxConformanceIssueCode;
16
+ readonly message: string;
17
+ readonly severity: "error" | "warning";
18
+ readonly part?: string;
19
+ readonly modelPath?: string;
20
+ readonly count?: number;
21
+ };
22
+ type FolioDocxConformanceCheck = {
23
+ readonly id: FolioDocxConformanceCheckId;
24
+ readonly status: FolioDocxConformanceCheckStatus;
25
+ };
26
+ type FolioDocxConformanceReport = {
27
+ readonly version: typeof FOLIO_DOCX_CONFORMANCE_REPORT_VERSION;
28
+ readonly profile: typeof FOLIO_DOCX_CONFORMANCE_PROFILE;
29
+ readonly status: FolioDocxConformanceStatus;
30
+ readonly conformanceClass: document_d_exports.DocxConformanceClass;
31
+ readonly checks: readonly FolioDocxConformanceCheck[];
32
+ readonly issues: readonly FolioDocxConformanceIssue[];
33
+ readonly unverifiedStandardsDimensions: readonly ["complete-schema-constraints", "markup-compatibility-processing", "consumer-specific-rendering"];
34
+ };
35
+ type ValidateDocxConformanceOptions = {
36
+ readonly archive?: DocxArchiveOptions;
37
+ };
38
+ /** Assess a DOCX package against Folio's named, versioned support profile. */
39
+ declare const validateDocxConformance: (bytes: ArrayBuffer | Uint8Array, options?: ValidateDocxConformanceOptions) => Promise<FolioDocxConformanceReport>;
40
+ //#endregion
41
+ export { FOLIO_DOCX_CONFORMANCE_CHECKS, FOLIO_DOCX_CONFORMANCE_ISSUE_CODES, FOLIO_DOCX_CONFORMANCE_PROFILE, FOLIO_DOCX_CONFORMANCE_REPORT_VERSION, FolioDocxConformanceCheck, FolioDocxConformanceCheckId, FolioDocxConformanceCheckStatus, FolioDocxConformanceIssue, FolioDocxConformanceIssueCode, FolioDocxConformanceReport, FolioDocxConformanceStatus, ValidateDocxConformanceOptions, validateDocxConformance };
@@ -0,0 +1,336 @@
1
+ import { findChildByLocalName, getAttributeAnyPrefix, getLocalName, getNamespacePrefix, parseXmlDocument } from "../xmlParser.js";
2
+ import { detectDocxConformanceClass } from "../conformance.js";
3
+ import { DocxModelValidationError, validateFolioDocumentModel } from "../modelValidation.js";
4
+ import { DOCX_CONTAINER_TYPES, detectDocxContainerType } from "../encryption/containerFormat.js";
5
+ import { DocxParseError, parseDocx } from "../parser.js";
6
+ import { DocxArchiveError, loadDocxArchive } from "./boundedArchive.js";
7
+ import { getDocxXmlSafetyIssue } from "../xmlSafety.js";
8
+ import { DOCX_CONFORMANCE_CLASSES } from "@stll/docx-core/model";
9
+ //#region src/docx/server/validateDocxConformance.ts
10
+ const FOLIO_DOCX_CONFORMANCE_REPORT_VERSION = 1;
11
+ const FOLIO_DOCX_CONFORMANCE_PROFILE = "folio-supported-v1";
12
+ const FOLIO_DOCX_CONFORMANCE_CHECKS = Object.freeze([
13
+ "archive-safety",
14
+ "required-parts",
15
+ "xml-well-formedness",
16
+ "package-roots",
17
+ "conformance-class",
18
+ "canonical-model"
19
+ ]);
20
+ const FOLIO_DOCX_CONFORMANCE_ISSUE_CODES = Object.freeze([
21
+ "archive-load-failed",
22
+ "archive-invalid-options",
23
+ "archive-input-too-large",
24
+ "archive-too-many-entries",
25
+ "archive-entry-too-large",
26
+ "archive-total-too-large",
27
+ "required-part-missing",
28
+ "xml-doctype-forbidden",
29
+ "xml-not-well-formed",
30
+ "xml-read-failed",
31
+ "required-xml-unreadable",
32
+ "package-root-invalid",
33
+ "conformance-class-unknown",
34
+ "model-invalid",
35
+ "model-warning",
36
+ "parser-recovery",
37
+ "parser-unsupported",
38
+ "parser-failed",
39
+ "encrypted-container",
40
+ "container-not-zip"
41
+ ]);
42
+ const REQUIRED_PARTS = Object.freeze([
43
+ "[Content_Types].xml",
44
+ "_rels/.rels",
45
+ "word/document.xml"
46
+ ]);
47
+ const XML_PART_PATTERN = /(?:\.xml|\.rels)$/u;
48
+ const DOCUMENT_CONTENT_TYPE = "application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml";
49
+ const CONTENT_TYPES_NAMESPACE = "http://schemas.openxmlformats.org/package/2006/content-types";
50
+ const RELATIONSHIPS_NAMESPACE = "http://schemas.openxmlformats.org/package/2006/relationships";
51
+ const UNVERIFIED_STANDARDS_DIMENSIONS = Object.freeze([
52
+ "complete-schema-constraints",
53
+ "markup-compatibility-processing",
54
+ "consumer-specific-rendering"
55
+ ]);
56
+ const createMutableReport = () => ({
57
+ conformanceClass: DOCX_CONFORMANCE_CLASSES.UNKNOWN,
58
+ checks: new Map(FOLIO_DOCX_CONFORMANCE_CHECKS.map((id) => [id, "not-run"])),
59
+ issues: []
60
+ });
61
+ const addIssue = (report, issue) => {
62
+ report.issues.push(issue);
63
+ };
64
+ const finishReport = (report) => {
65
+ const checks = FOLIO_DOCX_CONFORMANCE_CHECKS.map((id) => ({
66
+ id,
67
+ status: report.checks.get(id) ?? "not-run"
68
+ }));
69
+ const hasFailedCheck = checks.some(({ status }) => status === "failed");
70
+ const hasIndeterminateCheck = checks.some(({ status }) => status === "indeterminate");
71
+ let status = "conformant";
72
+ if (hasFailedCheck) status = "invalid";
73
+ else if (hasIndeterminateCheck || checks.some(({ status: checkStatus }) => checkStatus === "not-run")) status = "indeterminate";
74
+ return {
75
+ version: 1,
76
+ profile: FOLIO_DOCX_CONFORMANCE_PROFILE,
77
+ status,
78
+ conformanceClass: report.conformanceClass,
79
+ checks,
80
+ issues: report.issues,
81
+ unverifiedStandardsDimensions: UNVERIFIED_STANDARDS_DIMENSIONS
82
+ };
83
+ };
84
+ const validateRequiredParts = (archive, report) => {
85
+ const entries = new Set(archive.entries);
86
+ const missingParts = REQUIRED_PARTS.filter((path) => !entries.has(path));
87
+ if (missingParts.length === 0) {
88
+ report.checks.set("required-parts", "passed");
89
+ return true;
90
+ }
91
+ report.checks.set("required-parts", "failed");
92
+ for (const part of missingParts) addIssue(report, {
93
+ check: "required-parts",
94
+ code: "required-part-missing",
95
+ message: "A required package part is missing.",
96
+ severity: "error",
97
+ part
98
+ });
99
+ return false;
100
+ };
101
+ const validateXmlParts = async (archive, report) => {
102
+ const xmlByPath = /* @__PURE__ */ new Map();
103
+ let hasInvalidXml = false;
104
+ for (const part of archive.entries.filter((path) => XML_PART_PATTERN.test(path)).toSorted()) {
105
+ const xml = await archive.readEntryString(part);
106
+ if (xml === null) continue;
107
+ xmlByPath.set(part, xml);
108
+ const safetyIssue = getDocxXmlSafetyIssue(xml);
109
+ if (safetyIssue === "doctype-forbidden") {
110
+ hasInvalidXml = true;
111
+ addIssue(report, {
112
+ check: "xml-well-formedness",
113
+ code: "xml-doctype-forbidden",
114
+ message: "XML package parts must not declare a document type.",
115
+ severity: "error",
116
+ part
117
+ });
118
+ continue;
119
+ }
120
+ if (safetyIssue === "not-well-formed") {
121
+ hasInvalidXml = true;
122
+ addIssue(report, {
123
+ check: "xml-well-formedness",
124
+ code: "xml-not-well-formed",
125
+ message: "An XML package part is not well formed.",
126
+ severity: "error",
127
+ part
128
+ });
129
+ }
130
+ }
131
+ if (hasInvalidXml) {
132
+ report.checks.set("xml-well-formedness", "failed");
133
+ return null;
134
+ }
135
+ report.checks.set("xml-well-formedness", "passed");
136
+ const contentTypesXml = xmlByPath.get("[Content_Types].xml");
137
+ const packageRelationshipsXml = xmlByPath.get("_rels/.rels");
138
+ const documentXml = xmlByPath.get("word/document.xml");
139
+ if (contentTypesXml === void 0 || packageRelationshipsXml === void 0 || documentXml === void 0) {
140
+ report.checks.set("xml-well-formedness", "indeterminate");
141
+ addIssue(report, {
142
+ check: "xml-well-formedness",
143
+ code: "required-xml-unreadable",
144
+ message: "A required XML package part could not be read.",
145
+ severity: "error"
146
+ });
147
+ return null;
148
+ }
149
+ return {
150
+ contentTypesXml,
151
+ packageRelationshipsXml,
152
+ documentXml
153
+ };
154
+ };
155
+ const hasRootNamespace = ({ name, attributes, expected }) => {
156
+ const prefix = getNamespacePrefix(name);
157
+ const attribute = prefix === null ? "xmlns" : `xmlns:${prefix}`;
158
+ return attributes?.[attribute] === expected;
159
+ };
160
+ const hasDocumentContentType = (contentTypesXml) => {
161
+ const root = parseXmlDocument(contentTypesXml);
162
+ if (root === null || getLocalName(root.name ?? "") !== "Types" || !hasRootNamespace({
163
+ name: root.name ?? "",
164
+ attributes: root.attributes,
165
+ expected: CONTENT_TYPES_NAMESPACE
166
+ })) return false;
167
+ return root.elements?.some((element) => element.type === "element" && getLocalName(element.name ?? "") === "Override" && getAttributeAnyPrefix(element, "PartName") === "/word/document.xml" && getAttributeAnyPrefix(element, "ContentType") === DOCUMENT_CONTENT_TYPE) ?? false;
168
+ };
169
+ const hasMainDocumentRelationship = (packageRelationshipsXml) => {
170
+ const root = parseXmlDocument(packageRelationshipsXml);
171
+ if (root === null || getLocalName(root.name ?? "") !== "Relationships" || !hasRootNamespace({
172
+ name: root.name ?? "",
173
+ attributes: root.attributes,
174
+ expected: RELATIONSHIPS_NAMESPACE
175
+ })) return false;
176
+ return root.elements?.some((element) => {
177
+ if (element.type !== "element" || getLocalName(element.name ?? "") !== "Relationship") return false;
178
+ const type = getAttributeAnyPrefix(element, "Type");
179
+ const target = getAttributeAnyPrefix(element, "Target")?.replace(/^\.?\//u, "");
180
+ return type?.endsWith("/officeDocument") === true && target === "word/document.xml" && getAttributeAnyPrefix(element, "TargetMode") !== "External";
181
+ }) ?? false;
182
+ };
183
+ const hasDocumentRoot = (documentXml) => {
184
+ const root = parseXmlDocument(documentXml);
185
+ return root !== null && getLocalName(root.name ?? "") === "document" && findChildByLocalName(root, "body") !== null;
186
+ };
187
+ const validatePackageRoots = (xml, report) => {
188
+ const invalidParts = [];
189
+ if (!hasDocumentContentType(xml.contentTypesXml)) invalidParts.push("[Content_Types].xml");
190
+ if (!hasMainDocumentRelationship(xml.packageRelationshipsXml)) invalidParts.push("_rels/.rels");
191
+ if (!hasDocumentRoot(xml.documentXml)) invalidParts.push("word/document.xml");
192
+ if (invalidParts.length === 0) {
193
+ report.checks.set("package-roots", "passed");
194
+ return true;
195
+ }
196
+ report.checks.set("package-roots", "failed");
197
+ for (const part of invalidParts) addIssue(report, {
198
+ check: "package-roots",
199
+ code: "package-root-invalid",
200
+ message: "A required package part does not declare the expected root or main document binding.",
201
+ severity: "error",
202
+ part
203
+ });
204
+ return false;
205
+ };
206
+ const validateConformanceClass = (documentXml, report) => {
207
+ report.conformanceClass = detectDocxConformanceClass(documentXml);
208
+ if (report.conformanceClass !== DOCX_CONFORMANCE_CLASSES.UNKNOWN) {
209
+ report.checks.set("conformance-class", "passed");
210
+ return;
211
+ }
212
+ report.checks.set("conformance-class", "indeterminate");
213
+ addIssue(report, {
214
+ check: "conformance-class",
215
+ code: "conformance-class-unknown",
216
+ message: "The main document namespace does not identify a supported conformance class.",
217
+ severity: "warning",
218
+ part: "word/document.xml"
219
+ });
220
+ };
221
+ const findModelValidationError = (error) => {
222
+ let current = error;
223
+ while (current instanceof Error) {
224
+ if (current instanceof DocxModelValidationError) return current;
225
+ current = "cause" in current ? current.cause : void 0;
226
+ }
227
+ return null;
228
+ };
229
+ const validateCanonicalModel = async (bytes, report) => {
230
+ try {
231
+ const document = await parseDocx(bytes, {
232
+ detectVariables: false,
233
+ preloadFonts: false
234
+ });
235
+ const validation = validateFolioDocumentModel(document);
236
+ const warningCount = document.warnings?.length ?? 0;
237
+ if (!validation.valid) report.checks.set("canonical-model", "failed");
238
+ else if (warningCount > 0) {
239
+ report.checks.set("canonical-model", "indeterminate");
240
+ addIssue(report, {
241
+ check: "canonical-model",
242
+ code: "parser-recovery",
243
+ message: "The canonical parser reported recovery or preservation warnings.",
244
+ severity: "warning",
245
+ count: warningCount
246
+ });
247
+ } else report.checks.set("canonical-model", "passed");
248
+ for (const issue of validation.issues) addIssue(report, {
249
+ check: "canonical-model",
250
+ code: issue.severity === "error" ? "model-invalid" : "model-warning",
251
+ message: issue.message,
252
+ severity: issue.severity,
253
+ modelPath: issue.path
254
+ });
255
+ } catch (error) {
256
+ const modelError = findModelValidationError(error);
257
+ if (modelError !== null) {
258
+ report.checks.set("canonical-model", "failed");
259
+ for (const issue of modelError.issues) addIssue(report, {
260
+ check: "canonical-model",
261
+ code: issue.severity === "error" ? "model-invalid" : "model-warning",
262
+ message: issue.message,
263
+ severity: issue.severity,
264
+ modelPath: issue.path
265
+ });
266
+ return;
267
+ }
268
+ report.checks.set("canonical-model", "indeterminate");
269
+ addIssue(report, {
270
+ check: "canonical-model",
271
+ code: error instanceof DocxParseError ? "parser-unsupported" : "parser-failed",
272
+ message: "The canonical document model could not be assessed.",
273
+ severity: "warning"
274
+ });
275
+ }
276
+ };
277
+ /** Assess a DOCX package against Folio's named, versioned support profile. */
278
+ const validateDocxConformance = async (bytes, options = {}) => {
279
+ const report = createMutableReport();
280
+ const containerType = detectDocxContainerType(bytes);
281
+ if (containerType === DOCX_CONTAINER_TYPES.CFB) {
282
+ report.checks.set("archive-safety", "indeterminate");
283
+ addIssue(report, {
284
+ check: "archive-safety",
285
+ code: "encrypted-container",
286
+ message: "Encrypted containers require decryption before this profile can assess them.",
287
+ severity: "warning"
288
+ });
289
+ return finishReport(report);
290
+ }
291
+ if (containerType !== DOCX_CONTAINER_TYPES.ZIP) {
292
+ report.checks.set("archive-safety", "failed");
293
+ addIssue(report, {
294
+ check: "archive-safety",
295
+ code: "container-not-zip",
296
+ message: "The input is not a supported DOCX package container.",
297
+ severity: "error"
298
+ });
299
+ return finishReport(report);
300
+ }
301
+ let archive;
302
+ try {
303
+ archive = await loadDocxArchive(bytes, options.archive);
304
+ report.checks.set("archive-safety", "passed");
305
+ } catch (error) {
306
+ report.checks.set("archive-safety", "failed");
307
+ addIssue(report, {
308
+ check: "archive-safety",
309
+ code: error instanceof DocxArchiveError ? `archive-${error.reason}` : "archive-load-failed",
310
+ message: "The DOCX package could not be loaded within the configured safety limits.",
311
+ severity: "error"
312
+ });
313
+ return finishReport(report);
314
+ }
315
+ if (!validateRequiredParts(archive, report)) return finishReport(report);
316
+ let xml;
317
+ try {
318
+ xml = await validateXmlParts(archive, report);
319
+ } catch (error) {
320
+ report.checks.set("xml-well-formedness", "indeterminate");
321
+ addIssue(report, {
322
+ check: "xml-well-formedness",
323
+ code: error instanceof DocxArchiveError ? `archive-${error.reason}` : "xml-read-failed",
324
+ message: "The XML package parts could not be read within the configured safety limits.",
325
+ severity: "warning"
326
+ });
327
+ return finishReport(report);
328
+ }
329
+ if (xml === null) return finishReport(report);
330
+ if (!validatePackageRoots(xml, report)) return finishReport(report);
331
+ validateConformanceClass(xml.documentXml, report);
332
+ await validateCanonicalModel(bytes, report);
333
+ return finishReport(report);
334
+ };
335
+ //#endregion
336
+ export { FOLIO_DOCX_CONFORMANCE_CHECKS, FOLIO_DOCX_CONFORMANCE_ISSUE_CODES, FOLIO_DOCX_CONFORMANCE_PROFILE, FOLIO_DOCX_CONFORMANCE_REPORT_VERSION, validateDocxConformance };
@@ -1,4 +1,4 @@
1
- import { findChild, getAttribute, parseBooleanElement, parseXmlDocument } from "./xmlParser.js";
1
+ import { findChild, findChildren, getAttribute, parseBooleanElement, parseXmlDocument } from "./xmlParser.js";
2
2
  //#region src/docx/settingsParser.ts
3
3
  /** OOXML default per §17.6.13 when `w:defaultTabStop` is absent. */
4
4
  const DEFAULT_TAB_STOP_TWIPS = 720;
@@ -35,6 +35,8 @@ function parseSettings(xml) {
35
35
  const noLineBreaksBefore = parseKinsokuOverride(root, "noLineBreaksBefore");
36
36
  const noLineBreaksAfter = parseKinsokuOverride(root, "noLineBreaksAfter");
37
37
  const compat = root ? findChild(root, "w", "compat") : null;
38
+ const compatibilityMode = parseCompatibilityMode(compat);
39
+ if (compatibilityMode !== void 0) settings.compatibilityMode = compatibilityMode;
38
40
  const splitPageBreakAndParagraphMark = compat ? findChild(compat, "w", "splitPgBreakAndParaMark") : null;
39
41
  if (splitPageBreakAndParagraphMark && parseBooleanElement(splitPageBreakAndParagraphMark)) settings.splitPageBreakAndParagraphMark = true;
40
42
  const applyBreakingRules = compat ? findChild(compat, "w", "applyBreakingRules") : null;
@@ -46,6 +48,14 @@ function parseSettings(xml) {
46
48
  };
47
49
  return settings;
48
50
  }
51
+ function parseCompatibilityMode(compat) {
52
+ if (!compat) return;
53
+ const setting = findChildren(compat, "w", "compatSetting").find((candidate) => getAttribute(candidate, "w", "name") === "compatibilityMode");
54
+ const raw = setting ? getAttribute(setting, "w", "val") : null;
55
+ if (raw === null || !/^\d+$/u.test(raw)) return;
56
+ const parsed = Number.parseInt(raw, 10);
57
+ return Number.isSafeInteger(parsed) ? parsed : void 0;
58
+ }
49
59
  function parseOnOffSetting(root, name) {
50
60
  if (!root) return;
51
61
  const element = findChild(root, "w", name);
@@ -1,5 +1,5 @@
1
- import { findAllDeep, findChildByLocalName, findChildrenByLocalName, getAttribute, parseNumericAttribute } from "./xmlParser.js";
2
1
  import { ShapeOutlineStyleSchema, ShapeTypeSchema, narrowEnum } from "./parserEnums.js";
2
+ import { findAllDeep, findChildByLocalName, findChildrenByLocalName, getAttribute, parseNumericAttribute } from "./xmlParser.js";
3
3
  import { parseAnchorPosition, parseAnchorWrap, parseColorElement, parseFill } from "./drawingUtils.js";
4
4
  //#region src/docx/shapeParser.ts
5
5
  /** Convert OOXML rotation (1/60000ths of a degree) to degrees. */
@@ -1,8 +1,8 @@
1
- import { findChild, findChildren, getAttribute, getLocalName, parseBooleanElement, parseNumericAttribute, parseTableMeasurementValue, parseXmlDocument } from "./xmlParser.js";
2
1
  import { BorderStyleSchema, ConditionalStyleTypeSchema, EmphasisMarkSchema, FontThemeSchema, HighlightColorSchema, LineSpacingRuleSchema, ParagraphAlignmentSchema, ShadingPatternSchema, StyleTypeSchema, TabLeaderSchema, TabStopAlignmentSchema, TableCellTextDirectionSchema, TableRowHeightRuleSchema, TableWidthTypeSchema, TextEffectSchema, ThemeColorSlotSchema, UnderlineStyleSchema, narrowEnum } from "./parserEnums.js";
3
- import { resolveThemeFontRef } from "./themeParser.js";
2
+ import { findChild, findChildren, getAttribute, getLocalName, parseBooleanElement, parseNumericAttribute, parseTableMeasurementValue, parseXmlDocument } from "./xmlParser.js";
4
3
  import { mergeTextFormatting } from "../utils/textFormattingMerge.js";
5
4
  import { mergeParagraphFormatting } from "../utils/paragraphFormattingMerge.js";
5
+ import { resolveThemeFontRef } from "./themeParser.js";
6
6
  //#region src/docx/styleParser.ts
7
7
  /**
8
8
  * Parse text formatting properties (w:rPr)
@@ -263,6 +263,8 @@ function parseParagraphProperties(pPr, theme) {
263
263
  }
264
264
  const bidi = findChild(pPr, "w", "bidi");
265
265
  if (bidi) formatting.bidi = parseBooleanElement(bidi);
266
+ const snapToGrid = findChild(pPr, "w", "snapToGrid");
267
+ if (snapToGrid) formatting.snapToGrid = parseBooleanElement(snapToGrid);
266
268
  const spacing = findChild(pPr, "w", "spacing");
267
269
  if (spacing) {
268
270
  const before = parseNumericAttribute(spacing, "w", "before");
@@ -1,6 +1,6 @@
1
+ import { BorderStyleSchema, FloatingTableXSpecSchema, FloatingTableYSpecSchema, ShadingPatternSchema, TableCellTextDirectionSchema, ThemeColorSlotSchema, narrowEnum } from "./parserEnums.js";
1
2
  import { findChild, findChildByLocalName, findChildren, getAttribute, getChildElements, mergeXmlnsDeclarations, parseBooleanElement, parseNumericAttribute, parseTableMeasurementValue } from "./xmlParser.js";
2
3
  import { parseBookmarkEnd, parseBookmarkStart } from "./bookmarkParser.js";
3
- import { BorderStyleSchema, FloatingTableXSpecSchema, FloatingTableYSpecSchema, ShadingPatternSchema, TableCellTextDirectionSchema, ThemeColorSlotSchema, narrowEnum } from "./parserEnums.js";
4
4
  import { parseParagraph } from "./paragraphParser.js";
5
5
  import { appendBookmarkMarkerToLastParagraphInBlocks, appendBookmarkMarkerToLastParagraphInCells, prependBookmarkMarkersToFirstParagraphInBlocks, prependBookmarkMarkersToFirstParagraphInCell } from "./bookmarkPlacement.js";
6
6
  import { enrichParagraphTextBoxes } from "./paragraphTextBoxEnrichment.js";
@@ -349,10 +349,20 @@ function parseTableCellStructuralChange(tcPrElement) {
349
349
  info: parseTrackedChangeInfo(deletion)
350
350
  };
351
351
  const merge = findChild(tcPrElement, "w", "cellMerge");
352
- if (merge) return {
353
- type: "tableCellMerge",
354
- info: parseTrackedChangeInfo(merge)
355
- };
352
+ if (merge) {
353
+ const verticalMerge = parseTableCellVerticalMergeRevisionValue(getAttribute(merge, "w", "vMerge"));
354
+ const verticalMergeOriginal = parseTableCellVerticalMergeRevisionValue(getAttribute(merge, "w", "vMergeOrig"));
355
+ return {
356
+ type: "tableCellMerge",
357
+ info: parseTrackedChangeInfo(merge),
358
+ ...verticalMerge !== void 0 ? { verticalMerge } : {},
359
+ ...verticalMergeOriginal !== void 0 ? { verticalMergeOriginal } : {}
360
+ };
361
+ }
362
+ }
363
+ function parseTableCellVerticalMergeRevisionValue(value) {
364
+ if (value === "cont") return "continue";
365
+ if (value === "rest") return "rest";
356
366
  }
357
367
  /**
358
368
  * Parse table row properties (w:trPr)
@@ -482,7 +492,8 @@ function parseTableCellProperties(tcPrElement) {
482
492
  else formatting.vMerge = "continue";
483
493
  if (parseBooleanElement(findChild(tcPrElement, "w", "tcFitText"))) formatting.fitText = true;
484
494
  if (parseBooleanElement(findChild(tcPrElement, "w", "noWrap"))) formatting.noWrap = true;
485
- if (parseBooleanElement(findChild(tcPrElement, "w", "hideMark"))) formatting.hideMark = true;
495
+ const hideMark = parseBooleanElement(findChild(tcPrElement, "w", "hideMark"));
496
+ if (hideMark !== void 0) formatting.hideMark = hideMark;
486
497
  const conditionalFormat = parseConditionalFormatStyle(findChild(tcPrElement, "w", "cnfStyle"));
487
498
  if (conditionalFormat) formatting.conditionalFormat = conditionalFormat;
488
499
  if (Object.keys(formatting).length === 0) return;
@@ -508,7 +519,7 @@ function parseCellContent(tcElement, styles, theme, numbering, rels, media, opti
508
519
  const localName = getLocalName(child.name);
509
520
  if (localName === "p") {
510
521
  const para = parseParagraph(child, styles, theme, numbering, rels, media, options);
511
- enrichParagraphTextBoxes(para, child, styles, theme, numbering, rels, media);
522
+ enrichParagraphTextBoxes(para, child, styles, theme, numbering, rels, media, parseTable);
512
523
  prependPendingBookmarkMarkers(para, pendingBookmarkMarkers);
513
524
  content.push(para);
514
525
  } else if (localName === "tbl") {
@@ -15,16 +15,16 @@ declare function extractTextBoxContentElements(txbxContent: XmlElement | null):
15
15
  /**
16
16
  * Type for the paragraph parser function to avoid circular imports
17
17
  */
18
- type ParagraphParserFn = (node: XmlElement, styles: StyleMap | null, theme: document_d_exports.Theme | null, numbering: NumberingMap | null, rels?: document_d_exports.RelationshipMap | null) => document_d_exports.Paragraph;
18
+ type ParagraphParserFn = (node: XmlElement, styles: StyleMap | null, theme: document_d_exports.Theme | null, numbering: NumberingMap | null, rels: document_d_exports.RelationshipMap | null, media: Map<string, document_d_exports.MediaFile> | null) => document_d_exports.Paragraph;
19
19
  /**
20
20
  * Type for the table parser function to avoid circular imports
21
21
  */
22
- type TableParserFn = (node: XmlElement, styles: StyleMap | null, theme: document_d_exports.Theme | null, numbering: NumberingMap | null, rels?: document_d_exports.RelationshipMap | null, media?: Map<string, document_d_exports.MediaFile>) => document_d_exports.Table;
22
+ type TableParserFn = (node: XmlElement, styles: StyleMap | null, theme: document_d_exports.Theme | null, numbering: NumberingMap | null, rels: document_d_exports.RelationshipMap | null, media: Map<string, document_d_exports.MediaFile> | null) => document_d_exports.Table;
23
23
  /**
24
24
  * Parse text box content with provided parser functions
25
25
  * This avoids circular dependencies by accepting parser functions as parameters
26
26
  */
27
- declare function parseTextBoxContent(txbxContent: XmlElement | null, parseParagraph: ParagraphParserFn, parseTable: TableParserFn | null, styles: StyleMap | null, theme: document_d_exports.Theme | null, numbering: NumberingMap | null, rels?: document_d_exports.RelationshipMap | null, _media?: Map<string, document_d_exports.MediaFile>): document_d_exports.Paragraph[];
27
+ declare function parseTextBoxContent(txbxContent: XmlElement | null, parseParagraph: ParagraphParserFn, parseTable: TableParserFn | null, styles: StyleMap | null, theme: document_d_exports.Theme | null, numbering: NumberingMap | null, rels: document_d_exports.RelationshipMap | null, media: Map<string, document_d_exports.MediaFile> | null): (document_d_exports.Paragraph | document_d_exports.Table)[];
28
28
  /**
29
29
  * Check if a drawing element contains a text box
30
30
  * Text boxes are shapes with wps:txbx content
@@ -44,20 +44,20 @@ function extractTextBoxContentElements(txbxContent) {
44
44
  * Parse text box content with provided parser functions
45
45
  * This avoids circular dependencies by accepting parser functions as parameters
46
46
  */
47
- function parseTextBoxContent(txbxContent, parseParagraph, parseTable, styles, theme, numbering, rels, _media) {
47
+ function parseTextBoxContent(txbxContent, parseParagraph, parseTable, styles, theme, numbering, rels, media) {
48
48
  if (!txbxContent) return [];
49
- const paragraphs = [];
49
+ const blocks = [];
50
50
  const children = getChildElements(txbxContent);
51
51
  for (const child of children) {
52
52
  const name = child.name || "";
53
53
  const colonIdx = name.indexOf(":");
54
54
  const localName = colonIdx !== -1 ? name.slice(colonIdx + 1) : name;
55
55
  if (localName === "p") {
56
- const paragraph = parseParagraph(child, styles, theme, numbering, rels);
57
- paragraphs.push(paragraph);
58
- } else if (localName === "tbl" && parseTable) {}
56
+ const paragraph = parseParagraph(child, styles, theme, numbering, rels, media);
57
+ blocks.push(paragraph);
58
+ } else if (localName === "tbl" && parseTable) blocks.push(parseTable(child, styles, theme, numbering, rels, media));
59
59
  }
60
- return paragraphs;
60
+ return blocks;
61
61
  }
62
62
  /**
63
63
  * Check if a drawing element contains a text box
@@ -232,16 +232,20 @@ function hasTextBoxContent(textBox) {
232
232
  * Get plain text from text box (helper for search/indexing)
233
233
  */
234
234
  function getTextBoxText(textBox) {
235
- const parts = [];
236
- for (const paragraph of textBox.content) {
235
+ return textBox.content.map(getTextBoxBlockText).join("\n");
236
+ }
237
+ const getTextBoxBlockText = (block) => {
238
+ if (block.type === "paragraph") {
237
239
  const runTexts = [];
238
- for (const item of paragraph.content) if (item.type === "run") {
240
+ for (const item of block.content) {
241
+ if (item.type !== "run") continue;
239
242
  for (const content of item.content) if (content.type === "text") runTexts.push(content.text);
240
243
  }
241
- parts.push(runTexts.join(""));
244
+ return runTexts.join("");
242
245
  }
243
- return parts.join("\n");
244
- }
246
+ if (block.type === "table") return block.rows.map((row) => row.cells.map((cell) => cell.content.map(getTextBoxBlockText).join("\n")).join(" ")).join("\n");
247
+ return block.content.map(getTextBoxBlockText).join("\n");
248
+ };
245
249
  /**
246
250
  * Resolve fill color to CSS color string
247
251
  */
@@ -0,0 +1,5 @@
1
+ //#region src/docx/xmlSafety.d.ts
2
+ type DocxXmlSafetyIssue = "doctype-forbidden" | "not-well-formed";
3
+ declare const getDocxXmlSafetyIssue: (xml: string) => DocxXmlSafetyIssue | null;
4
+ //#endregion
5
+ export { DocxXmlSafetyIssue, getDocxXmlSafetyIssue };
@@ -0,0 +1,9 @@
1
+ import { XMLValidator } from "fast-xml-parser";
2
+ //#region src/docx/xmlSafety.ts
3
+ const DOCTYPE_PATTERN = /<!DOCTYPE(?:\s|>)/iu;
4
+ const getDocxXmlSafetyIssue = (xml) => {
5
+ if (DOCTYPE_PATTERN.test(xml)) return "doctype-forbidden";
6
+ return XMLValidator.validate(xml) === true ? null : "not-well-formed";
7
+ };
8
+ //#endregion
9
+ export { getDocxXmlSafetyIssue };
@@ -196,9 +196,7 @@ function collectFieldRuns(block, out) {
196
196
  if (block.kind === "paragraph") {
197
197
  for (const run of block.runs) if (run.kind === "field") out.push(run);
198
198
  } else if (block.kind === "table") for (const row of block.rows) for (const cell of row.cells) for (const cellBlock of cell.blocks) collectFieldRuns(cellBlock, out);
199
- else if (block.kind === "textBox") {
200
- for (const paragraph of block.content) for (const run of paragraph.runs) if (run.kind === "field") out.push(run);
201
- }
199
+ else if (block.kind === "textBox") for (const child of block.content) collectFieldRuns(child, out);
202
200
  }
203
201
  //#endregion
204
202
  export { buildHeaderFooterFieldValues, fieldValuesEqual, resolveFieldValues };